Refactor launcher library importing, manifests

Switched the library importing in the launcher to work based on a queue. As a new game is added to the queue, it should find any related data and also add it to the queue. This should result in an easier to maintain importer while reducing the load on the launcher's DAL. This may result in more RAM usage while importing. Local manifests have also been refactored to match manifests in import/export LCX files, removing the old manifest format. This is a large breaking change that will probably break existing game installations. A migration will probably have to be created.
This commit is contained in:
Pat Hartl 2025-11-29 18:24:27 -06:00
parent b7433a2da1
commit 6dfc0906ac
60 changed files with 2229 additions and 984 deletions

View file

@ -1,16 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace LANCommander.Launcher.Data.Enums
{
public enum GameType
{
[Display(Name = "Main Game")]
MainGame,
Expansion,
[Display(Name = "Standalone Expansion")]
StandaloneExpansion,
Mod,
[Display(Name = "Standalone Mod")]
StandaloneMod
}
}

View file

@ -0,0 +1,852 @@
// <auto-generated />
using System;
using LANCommander.Launcher.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace LANCommander.Launcher.Data.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20251130001620_AddImportedOn")]
partial class AddImportedOn
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("CategoryGame", b =>
{
b.Property<Guid>("CategoriesId")
.HasColumnType("TEXT");
b.Property<Guid>("GamesId")
.HasColumnType("TEXT");
b.HasKey("CategoriesId", "GamesId");
b.HasIndex("GamesId");
b.ToTable("CategoryGame");
});
modelBuilder.Entity("CollectionGame", b =>
{
b.Property<Guid>("CollectionId")
.HasColumnType("TEXT");
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.HasKey("CollectionId", "GameId");
b.HasIndex("GameId");
b.ToTable("CollectionGame");
});
modelBuilder.Entity("GameDeveloper", b =>
{
b.Property<Guid>("DeveloperId")
.HasColumnType("TEXT");
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.HasKey("DeveloperId", "GameId");
b.HasIndex("GameId");
b.ToTable("GameDeveloper");
});
modelBuilder.Entity("GameGenre", b =>
{
b.Property<Guid>("GamesId")
.HasColumnType("TEXT");
b.Property<Guid>("GenresId")
.HasColumnType("TEXT");
b.HasKey("GamesId", "GenresId");
b.HasIndex("GenresId");
b.ToTable("GameGenre");
});
modelBuilder.Entity("GamePlatform", b =>
{
b.Property<Guid>("GamesId")
.HasColumnType("TEXT");
b.Property<Guid>("PlatformsId")
.HasColumnType("TEXT");
b.HasKey("GamesId", "PlatformsId");
b.HasIndex("PlatformsId");
b.ToTable("GamePlatform");
});
modelBuilder.Entity("GamePublisher", b =>
{
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.Property<Guid>("PublisherId")
.HasColumnType("TEXT");
b.HasKey("GameId", "PublisherId");
b.HasIndex("PublisherId");
b.ToTable("GamePublisher");
});
modelBuilder.Entity("GameRedistributable", b =>
{
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.Property<Guid>("RedistributableId")
.HasColumnType("TEXT");
b.HasKey("GameId", "RedistributableId");
b.HasIndex("RedistributableId");
b.ToTable("GameRedistributable");
});
modelBuilder.Entity("GameTag", b =>
{
b.Property<Guid>("GamesId")
.HasColumnType("TEXT");
b.Property<Guid>("TagsId")
.HasColumnType("TEXT");
b.HasKey("GamesId", "TagsId");
b.HasIndex("TagsId");
b.ToTable("GameTag");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("ParentId")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("Categories");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Collections");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Companies");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Engines");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid?>("BaseGameId")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<Guid?>("EngineId")
.HasColumnType("TEXT");
b.Property<long?>("IGDBId")
.HasColumnType("INTEGER");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("InstallDirectory")
.HasColumnType("TEXT");
b.Property<bool>("Installed")
.HasColumnType("INTEGER");
b.Property<DateTime?>("InstalledOn")
.HasColumnType("TEXT");
b.Property<string>("InstalledVersion")
.HasColumnType("TEXT");
b.Property<string>("LatestVersion")
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<DateTime?>("ReleasedOn")
.HasColumnType("TEXT");
b.Property<bool>("Singleplayer")
.HasColumnType("INTEGER");
b.Property<string>("SortTitle")
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("BaseGameId");
b.HasIndex("EngineId");
b.ToTable("Games");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Genre", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Genres");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Libraries");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Crc32")
.IsRequired()
.HasMaxLength(8)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("FileId")
.HasColumnType("TEXT");
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("MimeType")
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(64)
.HasColumnType("TEXT");
b.Property<string>("SourceUrl")
.HasMaxLength(2048)
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GameId");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Media");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<int>("MaxPlayers")
.HasColumnType("INTEGER");
b.Property<int>("MinPlayers")
.HasColumnType("INTEGER");
b.Property<int>("NetworkProtocol")
.HasColumnType("INTEGER");
b.Property<int>("Spectators")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GameId");
b.ToTable("MultiplayerModes");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Platform", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Platforms");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime?>("End")
.HasColumnType("TEXT");
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime?>("Start")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GameId");
b.ToTable("PlaySessions");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Redistributables");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Tags");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Alias")
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
b.Property<string>("UserName")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("LibraryGame", b =>
{
b.Property<Guid>("GameId")
.HasColumnType("TEXT");
b.Property<Guid>("LibraryId")
.HasColumnType("TEXT");
b.HasKey("GameId", "LibraryId");
b.HasIndex("LibraryId");
b.ToTable("LibraryGame");
});
modelBuilder.Entity("CategoryGame", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Category", null)
.WithMany()
.HasForeignKey("CategoriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("CollectionGame", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Collection", null)
.WithMany()
.HasForeignKey("CollectionId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GameDeveloper", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Company", null)
.WithMany()
.HasForeignKey("DeveloperId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GameGenre", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Genre", null)
.WithMany()
.HasForeignKey("GenresId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GamePlatform", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Platform", null)
.WithMany()
.HasForeignKey("PlatformsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GamePublisher", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Company", null)
.WithMany()
.HasForeignKey("PublisherId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GameRedistributable", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Redistributable", null)
.WithMany()
.HasForeignKey("RedistributableId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("GameTag", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GamesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Tag", null)
.WithMany()
.HasForeignKey("TagsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Category", "Parent")
.WithMany("Children")
.HasForeignKey("ParentId");
b.Navigation("Parent");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", "BaseGame")
.WithMany("DependentGames")
.HasForeignKey("BaseGameId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("LANCommander.Launcher.Data.Models.Engine", "Engine")
.WithMany("Games")
.HasForeignKey("EngineId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("BaseGame");
b.Navigation("Engine");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Library", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.User", "User")
.WithOne("Library")
.HasForeignKey("LANCommander.Launcher.Data.Models.Library", "UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game")
.WithMany("Media")
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("LANCommander.Launcher.Data.Models.User", "User")
.WithOne("Avatar")
.HasForeignKey("LANCommander.Launcher.Data.Models.Media", "UserId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Game");
b.Navigation("User");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.MultiplayerMode", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game")
.WithMany("MultiplayerModes")
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Game");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", "Game")
.WithMany("PlaySessions")
.HasForeignKey("GameId");
b.Navigation("Game");
});
modelBuilder.Entity("LibraryGame", b =>
{
b.HasOne("LANCommander.Launcher.Data.Models.Game", null)
.WithMany()
.HasForeignKey("GameId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("LANCommander.Launcher.Data.Models.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b =>
{
b.Navigation("Children");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b =>
{
b.Navigation("Games");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b =>
{
b.Navigation("DependentGames");
b.Navigation("Media");
b.Navigation("MultiplayerModes");
b.Navigation("PlaySessions");
});
modelBuilder.Entity("LANCommander.Launcher.Data.Models.User", b =>
{
b.Navigation("Avatar");
b.Navigation("Library");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,180 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Launcher.Data.Migrations
{
/// <inheritdoc />
public partial class AddImportedOn : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Users",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Tags",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Redistributables",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "PlaySessions",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Platforms",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "MultiplayerModes",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Media",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Libraries",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Genres",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AlterColumn<DateTime>(
name: "ImportedOn",
table: "Games",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Engines",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Companies",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Collections",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "ImportedOn",
table: "Categories",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Users");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Tags");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Redistributables");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "PlaySessions");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Platforms");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "MultiplayerModes");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Media");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Libraries");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Genres");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Engines");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Companies");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Collections");
migrationBuilder.DropColumn(
name: "ImportedOn",
table: "Categories");
migrationBuilder.AlterColumn<DateTime>(
name: "ImportedOn",
table: "Games",
type: "TEXT",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "TEXT");
}
}
}

View file

@ -15,7 +15,7 @@ namespace LANCommander.Launcher.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.1");
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("CategoryGame", b =>
{
@ -146,6 +146,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -172,6 +175,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -193,6 +199,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -214,6 +223,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -247,7 +259,7 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<long?>("IGDBId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ImportedOn")
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("InstallDirectory")
@ -305,6 +317,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -326,6 +341,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");
@ -360,6 +378,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("MimeType")
.HasMaxLength(255)
.HasColumnType("TEXT");
@ -406,6 +427,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<int>("MaxPlayers")
.HasColumnType("INTEGER");
@ -440,6 +464,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -467,6 +494,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime?>("Start")
.HasColumnType("TEXT");
@ -495,6 +525,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<string>("Description")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -519,6 +552,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
@ -543,6 +579,9 @@ namespace LANCommander.Launcher.Data.Migrations
b.Property<DateTime>("CreatedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("ImportedOn")
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedOn")
.HasColumnType("TEXT");

View file

@ -8,6 +8,9 @@ namespace LANCommander.Launcher.Data.Models
{
[Key]
public Guid Id { get; set; }
[Display(Name = "Imported On")]
public DateTime ImportedOn { get; set; }
[Display(Name = "Created On")]
public DateTime CreatedOn { get; set; }

View file

@ -1,6 +1,6 @@
using LANCommander.Launcher.Data.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using LANCommander.SDK.Enums;
namespace LANCommander.Launcher.Data.Models
{
@ -23,8 +23,6 @@ namespace LANCommander.Launcher.Data.Models
[Display(Name = "Released On")]
public DateTime? ReleasedOn { get; set; }
public DateTime? ImportedOn { get; set; }
public GameType Type { get; set; }
public Guid? BaseGameId { get; set; }

View file

@ -1,5 +1,4 @@
using LANCommander.Launcher.Data.Enums;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Enums;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

View file

@ -1,5 +1,4 @@
using LANCommander.Launcher.Data.Enums;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Enums;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;

View file

@ -9,7 +9,7 @@ namespace LANCommander.Launcher.Models
{
public class ActionSelectorDialogOptions
{
public IEnumerable<SDK.Models.Action> Actions { get; set; }
public IEnumerable<SDK.Models.Manifest.Action> Actions { get; set; }
public string Title { get; set; }
public Game Game { get; set; }
}

View file

@ -123,14 +123,14 @@ namespace LANCommander.Launcher.Services
try
{
var game = await gameService.GetAsync(options.GameId);
var manifest = await ManifestHelper.ReadAsync<GameManifest>(game.InstallDirectory, game.Id);
/*var game = await gameService.GetAsync(options.GameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(game.InstallDirectory, game.Id);
var action = manifest.Actions.FirstOrDefault(a => a.Id == options.ActionId);
if (action == null)
action = manifest.Actions.OrderBy(a => a.SortOrder).FirstOrDefault(a => a.IsPrimaryAction);
await gameService.Run(game, action);
await gameService.Run(game, action);*/
}
catch (Exception ex)
{
@ -142,7 +142,7 @@ namespace LANCommander.Launcher.Services
{
Logger.LogInformation("Syncing games from server...");
importService.OnImportComplete += async () =>
/*importService.OnImportComplete += async () =>
{
Logger.LogInformation("Sync complete!");
};
@ -150,7 +150,7 @@ namespace LANCommander.Launcher.Services
importService.OnImportFailed += async (Exception ex) =>
{
Logger.LogError(ex, "Sync failed!");
};
};*/
await importService.ImportAsync();
}

View file

@ -0,0 +1,17 @@
namespace LANCommander.Launcher.Services.Exceptions;
public class ImportSkippedException<TRecord> : Exception where TRecord : class
{
public TRecord Record { get; set; }
public ImportSkippedException(TRecord record, string message) : base(message)
{
Record = record;
}
public ImportSkippedException(TRecord record, string message, Exception innerException) : base(message,
innerException)
{
Record = record;
}
}

View file

@ -1,4 +1,7 @@
using LANCommander.Launcher.Data;
using LANCommander.Launcher.Services.Import;
using LANCommander.Launcher.Services.Import.Factories;
using LANCommander.Launcher.Services.Import.Importers;
using LANCommander.Launcher.Services.PowerShell;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@ -17,7 +20,10 @@ namespace LANCommander.Launcher.Services.Extensions
{
public static IServiceCollection AddLANCommanderLauncher(this IServiceCollection services, Action<LANCommanderOptions> configure)
{
services.AddDbContext<DbContext, DatabaseContext>();
services.AddDbContext<DbContext, DatabaseContext>(options =>
{
options.EnableSensitiveDataLogging();
});
#region Register Client
var options = new LANCommanderOptions();
@ -55,6 +61,21 @@ namespace LANCommander.Launcher.Services.Extensions
services.AddScoped<SaveService>();
services.AddScoped<TagService>();
services.AddScoped<UpdateService>();
#region Import
services.AddScoped<ImportContextFactory>();
services.AddScoped<CollectionImporter>();
services.AddScoped<DeveloperImporter>();
services.AddScoped<EngineImporter>();
services.AddScoped<GameImporter>();
services.AddScoped<GenreImporter>();
services.AddScoped<MultiplayerModeImporter>();
services.AddScoped<PlatformImporter>();
services.AddScoped<PublisherImporter>();
services.AddScoped<TagImporter>();
#endregion
return services;
}

View file

@ -1,5 +1,6 @@
using LANCommander.Launcher.Data.Models;
using LANCommander.Launcher.Models;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Extensions;
using Microsoft.Extensions.Logging;
@ -116,7 +117,7 @@ namespace LANCommander.Launcher.Services
if (Filter.Installed)
items = items.Where(i => (i.DataItem as Game).Installed);
items = items.Where(i => (i.DataItem as Game).Type.ValueIsIn(Data.Enums.GameType.MainGame, Data.Enums.GameType.StandaloneExpansion, Data.Enums.GameType.StandaloneMod));
items = items.Where(i => (i.DataItem as Game).Type.ValueIsIn(GameType.MainGame, GameType.StandaloneExpansion, GameType.StandaloneMod));
op.Complete();
}

View file

@ -65,7 +65,7 @@ namespace LANCommander.Launcher.Services
}
}
public async Task Run(Game game, SDK.Models.Action action)
public async Task Run(Game game, SDK.Models.Manifest.Action action)
{
Guid userId;

View file

@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
namespace LANCommander.Launcher.Services.Import.Factories;
public class ImportContextFactory(IServiceProvider serviceProvider)
{
public ImportContext Create()
{
var scope = serviceProvider.CreateScope();
return scope.ServiceProvider.GetRequiredService<ImportContext>();
}
}

View file

@ -0,0 +1,9 @@
namespace LANCommander.Launcher.Services.Import;
public interface IImportItemInfo
{
string Key { get; set; }
string Type { get; set; }
string Name { get; set; }
bool Processed { get; set; }
}

View file

@ -0,0 +1,11 @@
namespace LANCommander.Launcher.Services.Import;
public interface IImporter<TRecord, TEntity> where TRecord : class
{
void UseContext(ImportContext importContext);
Task<ImportItemInfo<TRecord>> GetImportInfoAsync(TRecord record);
Task<bool> CanImportAsync(TRecord record);
Task<TEntity> AddAsync(TRecord record);
Task<TEntity> UpdateAsync(TRecord record);
Task<bool> ExistsAsync(TRecord record);
}

View file

@ -0,0 +1,150 @@
using LANCommander.Launcher.Services.Import.Importers;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
namespace LANCommander.Launcher.Services.Import;
public class ImportContext
{
public IImportItemInfo CurrentItem { get; set; }
public int Processed => _queue.Count(qi => qi.Processed);
public int Total => _queue.Count;
public EventHandler OnImportStarted { get; set; }
public EventHandler<ImportStatusUpdate> OnImportProgress { get; set; }
public EventHandler OnImportCompleted { get; set; }
private List<IImportItemInfo> _queue { get; set; } = new();
#region Importers
private CollectionImporter _collections;
private DeveloperImporter _developers;
private EngineImporter _engines;
private GameImporter _games;
private GenreImporter _genres;
private MultiplayerModeImporter _multiplayerModes;
private PlatformImporter _platforms;
private PublisherImporter _publishers;
private TagImporter _tags;
#endregion
public ImportContext(IServiceProvider serviceProvider)
{
_collections = serviceProvider.GetRequiredService<CollectionImporter>();
_developers = serviceProvider.GetRequiredService<DeveloperImporter>();
_engines = serviceProvider.GetRequiredService<EngineImporter>();
_games = serviceProvider.GetRequiredService<GameImporter>();
_genres = serviceProvider.GetRequiredService<GenreImporter>();
_multiplayerModes = serviceProvider.GetRequiredService<MultiplayerModeImporter>();
_platforms = serviceProvider.GetRequiredService<PlatformImporter>();
_publishers = serviceProvider.GetRequiredService<PublisherImporter>();
_tags = serviceProvider.GetRequiredService<TagImporter>();
}
public async Task AddAsync(Game game)
{
foreach (var collection in game.Collections)
if (!InQueue(collection, _collections) && await _collections.CanImportAsync(collection))
_queue.Add(await _collections.GetImportInfoAsync(collection));
foreach (var developer in game.Developers)
if (!InQueue(developer, _developers) && await _developers.CanImportAsync(developer))
_queue.Add(await _developers.GetImportInfoAsync(developer));
if (!InQueue(game.Engine, _engines) && await _engines.CanImportAsync(game.Engine))
_queue.Add(await _engines.GetImportInfoAsync(game.Engine));
foreach (var genre in game.Genres)
if (!InQueue(genre, _genres) && await _genres.CanImportAsync(genre))
_queue.Add(await _genres.GetImportInfoAsync(genre));
foreach (var multiplayerMode in game.MultiplayerModes)
if (!InQueue(multiplayerMode, _multiplayerModes) && await _multiplayerModes.CanImportAsync(multiplayerMode))
_queue.Add(await _multiplayerModes.GetImportInfoAsync(multiplayerMode));
foreach (var platform in game.Platforms)
if (!InQueue(platform, _platforms) && await _platforms.CanImportAsync(platform))
_queue.Add(await _platforms.GetImportInfoAsync(platform));
foreach (var publisher in game.Publishers)
if (!InQueue(publisher, _publishers) && await _publishers.CanImportAsync(publisher))
_queue.Add(await _publishers.GetImportInfoAsync(publisher));
foreach (var tag in game.Tags)
if (!InQueue(tag, _tags) && await _tags.CanImportAsync(tag))
_queue.Add(await _tags.GetImportInfoAsync(tag));
if (!InQueue(game, _games) && await _games.CanImportAsync(game))
_queue.Add(await _games.GetImportInfoAsync(game));
}
private bool InQueue<TRecord, TEntity>(TRecord record, BaseImporter<TRecord, TEntity> importer)
where TRecord : class =>
_queue.Any(qi => qi.Key == importer.GetKey(record));
public async Task ImportQueueAsync()
{
OnImportStarted?.Invoke(this, EventArgs.Empty);
// Import metadata before games
_queue = _queue
.OrderBy(qi => qi.Type == nameof(Game))
.ThenBy(qi => qi.Type)
.ThenBy(qi => qi.Name)
.ToList();
foreach (var queueItem in _queue)
{
CurrentItem = queueItem;
OnImportProgress?.Invoke(this, new ImportStatusUpdate
{
CurrentItem = CurrentItem,
Index = Processed,
Total = Total,
});
switch (queueItem.Type)
{
case nameof(Collection):
await _collections.ImportAsync(queueItem);
break;
case "Developer":
await _developers.ImportAsync(queueItem);
break;
case nameof(Engine):
await _engines.ImportAsync(queueItem);
break;
case nameof(Game):
await _games.ImportAsync(queueItem);
break;
case nameof(Genre):
await _genres.ImportAsync(queueItem);
break;
case nameof(MultiplayerMode):
await _multiplayerModes.ImportAsync(queueItem);
break;
case nameof(Platform):
await _platforms.ImportAsync(queueItem);
break;
case "Publisher":
await _publishers.ImportAsync(queueItem);
break;
case nameof(Tag):
await _tags.ImportAsync(queueItem);
break;
}
}
OnImportCompleted?.Invoke(this, EventArgs.Empty);
}
}

View file

@ -0,0 +1,10 @@
namespace LANCommander.Launcher.Services.Import;
public class ImportItemInfo<T> : IImportItemInfo where T : class
{
public string Key { get; set; }
public string Type { get; set; }
public string Name { get; set; }
public bool Processed { get; set; }
public T Record { get; set; }
}

View file

@ -1,8 +1,8 @@
namespace LANCommander.Launcher.Models;
namespace LANCommander.Launcher.Services.Import;
public class ImportStatusUpdate
{
public int Index { get; set; }
public int Total { get; set; }
public ImportItem CurrentItem { get; set; }
public IImportItemInfo CurrentItem { get; set; }
}

View file

@ -0,0 +1,37 @@
using LANCommander.Launcher.Services.Exceptions;
namespace LANCommander.Launcher.Services.Import.Importers;
public abstract class BaseImporter<TRecord, TEntity> : IImporter<TRecord, TEntity> where TRecord : class
{
protected ImportContext ImportContext { get; private set; }
public void UseContext(ImportContext importContext)
{
ImportContext = importContext;
}
public async Task<TEntity> ImportAsync(IImportItemInfo importItem)
{
if (importItem is ImportItemInfo<TRecord> importItemInfo)
{
if (await ExistsAsync(importItemInfo.Record))
{
importItem.Processed = true;
return await UpdateAsync(importItemInfo.Record);
}
importItem.Processed = true;
return await AddAsync(importItemInfo.Record);
}
throw new ImportSkippedException<TRecord>(null, "Import item record is not supported by this importer.");
}
public abstract string GetKey(TRecord record);
public abstract Task<ImportItemInfo<TRecord>> GetImportInfoAsync(TRecord record);
public abstract Task<bool> CanImportAsync(TRecord record);
public abstract Task<TEntity> AddAsync(TRecord record);
public abstract Task<TEntity> UpdateAsync(TRecord record);
public abstract Task<bool> ExistsAsync(TRecord record);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class CollectionImporter(
CollectionService collectionService,
ILogger<CollectionImporter> logger) : BaseImporter<Collection, Data.Models.Collection>
{
public override async Task<ImportItemInfo<Collection>> GetImportInfoAsync(Collection record)
{
return new ImportItemInfo<Collection>
{
Key = GetKey(record),
Name = record.Name,
Type = nameof(Collection),
Record = record,
};
}
public override string GetKey(Collection record) => $"{nameof(Collection)}/{record.Name}";
public override async Task<bool> CanImportAsync(Collection record)
{
var existing = await collectionService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Collection> AddAsync(Collection record)
{
var collection = new Data.Models.Collection
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await collectionService.AddAsync(collection);
}
catch (Exception ex)
{
throw new ImportSkippedException<Collection>(record, "An unknown error occurred while trying to add collection", ex);
}
}
public override async Task<Data.Models.Collection> UpdateAsync(Collection record)
{
var existing = await collectionService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Collection>(record, "An unknown error occurred while trying to update collection", ex);
}
}
public override async Task<bool> ExistsAsync(Collection record) => await collectionService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class DeveloperImporter(
CompanyService companyService,
ILogger<DeveloperImporter> logger) : BaseImporter<Company, Data.Models.Company>
{
public override async Task<ImportItemInfo<Company>> GetImportInfoAsync(Company record)
{
return new ImportItemInfo<Company>
{
Key = GetKey(record),
Name = record.Name,
Type = "Developer",
Record = record,
};
}
public override string GetKey(Company record) => $"{nameof(Company)}/{record.Name}";
public override async Task<bool> CanImportAsync(Company record)
{
var existing = await companyService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Company> AddAsync(Company record)
{
var company = new Data.Models.Company
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await companyService.AddAsync(company);
}
catch (Exception ex)
{
throw new ImportSkippedException<Company>(record, "An unknown error occurred while trying to add company", ex);
}
}
public override async Task<Data.Models.Company> UpdateAsync(Company record)
{
var existing = await companyService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Company>(record, "An unknown error occurred while trying to update company", ex);
}
}
public override async Task<bool> ExistsAsync(Company record) => await companyService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class EngineImporter(
EngineService engineService,
ILogger<EngineImporter> logger) : BaseImporter<Engine, Data.Models.Engine>
{
public override async Task<ImportItemInfo<Engine>> GetImportInfoAsync(Engine record)
{
return new ImportItemInfo<Engine>
{
Key = GetKey(record),
Name = record.Name,
Type = nameof(Engine),
Record = record,
};
}
public override string GetKey(Engine record) => $"{nameof(Engine)}/{record.Name}";
public override async Task<bool> CanImportAsync(Engine record)
{
var existing = await engineService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Engine> AddAsync(Engine record)
{
var engine = new Data.Models.Engine
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await engineService.AddAsync(engine);
}
catch (Exception ex)
{
throw new ImportSkippedException<Engine>(record, "An unknown error occurred while trying to add engine", ex);
}
}
public override async Task<Data.Models.Engine> UpdateAsync(Engine record)
{
var existing = await engineService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Engine>(record, "An unknown error occurred while trying to update engine", ex);
}
}
public override async Task<bool> ExistsAsync(Engine record) => await engineService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,87 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class GameImporter(
GameService gameService,
ILogger<GameImporter> logger) : BaseImporter<Game, Data.Models.Game>
{
public override async Task<ImportItemInfo<Game>> GetImportInfoAsync(Game record)
{
return new ImportItemInfo<Game>
{
Key = GetKey(record),
Name = record.Title,
Type = nameof(Game),
Record = record,
};
}
public override string GetKey(Game record) => $"{nameof(Game)}/{record.Id}";
public override async Task<bool> CanImportAsync(Game record)
{
var existing = await gameService.GetAsync(record.Id);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Game> AddAsync(Game record)
{
var game = new Data.Models.Game
{
Title = record.Title,
SortTitle = record.SortTitle,
Description = record.Description,
Notes = record.Notes,
ReleasedOn = record.ReleasedOn,
Singleplayer = record.Singleplayer,
Type = record.Type,
IGDBId = record.IGDBId,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await gameService.AddAsync(game);
}
catch (Exception ex)
{
throw new ImportSkippedException<Game>(record, "An unknown error occurred while trying to add game", ex);
}
}
public override async Task<Data.Models.Game> UpdateAsync(Game record)
{
var existing = await gameService.GetAsync(record.Id);
try
{
existing.Title = record.Title;
existing.SortTitle = record.SortTitle;
existing.Description = record.Description;
existing.Notes = record.Notes;
existing.ReleasedOn = record.ReleasedOn;
existing.Singleplayer = record.Singleplayer;
existing.Type = record.Type;
existing.IGDBId = record.IGDBId;
existing.CreatedOn = record.CreatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Game>(record, "An unknown error occurred while trying to update game", ex);
}
}
public override async Task<bool> ExistsAsync(Game record) => await gameService.ExistsAsync(record.Id);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class GenreImporter(
GenreService genreService,
ILogger<GenreImporter> logger) : BaseImporter<Genre, Data.Models.Genre>
{
public override async Task<ImportItemInfo<Genre>> GetImportInfoAsync(Genre record)
{
return new ImportItemInfo<Genre>
{
Key = GetKey(record),
Name = record.Name,
Type = nameof(Genre),
Record = record,
};
}
public override string GetKey(Genre record) => $"{nameof(Genre)}/{record.Name}";
public override async Task<bool> CanImportAsync(Genre record)
{
var existing = await genreService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Genre> AddAsync(Genre record)
{
var genre = new Data.Models.Genre
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await genreService.AddAsync(genre);
}
catch (Exception ex)
{
throw new ImportSkippedException<Genre>(record, "An unknown error occurred while trying to add genre", ex);
}
}
public override async Task<Data.Models.Genre> UpdateAsync(Genre record)
{
var existing = await genreService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Genre>(record, "An unknown error occurred while trying to update genre", ex);
}
}
public override async Task<bool> ExistsAsync(Genre record) => await genreService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,84 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class MultiplayerModeImporter(
MultiplayerModeService multiplayerModeService,
ILogger<MultiplayerModeImporter> logger) : BaseImporter<MultiplayerMode, Data.Models.MultiplayerMode>
{
public override async Task<ImportItemInfo<MultiplayerMode>> GetImportInfoAsync(MultiplayerMode record)
{
return new ImportItemInfo<MultiplayerMode>
{
Key = GetKey(record),
Name = $"{record.NetworkProtocol} - {record.Type}",
Type = nameof(MultiplayerMode),
Record = record,
};
}
public override string GetKey(MultiplayerMode record) => $"{nameof(Collection)}/{record.NetworkProtocol}:{record.Type}";
public override async Task<bool> CanImportAsync(MultiplayerMode record)
{
var existing = await multiplayerModeService.FirstOrDefaultAsync(mm => mm.Type == record.Type && mm.NetworkProtocol == record.NetworkProtocol);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.MultiplayerMode> AddAsync(MultiplayerMode record)
{
var multiplayerMode = new Data.Models.MultiplayerMode
{
Type = record.Type,
Description = record.Description,
MaxPlayers = record.MaxPlayers,
MinPlayers = record.MinPlayers,
NetworkProtocol = record.NetworkProtocol,
Spectators = record.Spectators,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await multiplayerModeService.AddAsync(multiplayerMode);
}
catch (Exception ex)
{
throw new ImportSkippedException<MultiplayerMode>(record, "An unknown error occurred while trying to add multiplayer mode", ex);
}
}
public override async Task<Data.Models.MultiplayerMode> UpdateAsync(MultiplayerMode record)
{
var existing = await multiplayerModeService.FirstOrDefaultAsync(mm => mm.Type == record.Type && mm.NetworkProtocol == record.NetworkProtocol);
try
{
existing.Type = record.Type;
existing.Description = record.Description;
existing.MaxPlayers = record.MaxPlayers;
existing.MinPlayers = record.MinPlayers;
existing.NetworkProtocol = record.NetworkProtocol;
existing.Spectators = record.Spectators;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<MultiplayerMode>(record, "An unknown error occurred while trying to update multiplayer mode", ex);
}
}
public override async Task<bool> ExistsAsync(MultiplayerMode record) => await multiplayerModeService.ExistsAsync(mm => mm.Type == record.Type && mm.NetworkProtocol == record.NetworkProtocol);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class PlatformImporter(
PlatformService platformService,
ILogger<PlatformImporter> logger) : BaseImporter<Platform, Data.Models.Platform>
{
public override async Task<ImportItemInfo<Platform>> GetImportInfoAsync(Platform record)
{
return new ImportItemInfo<Platform>
{
Key = GetKey(record),
Name = record.Name,
Type = nameof(Platform),
Record = record,
};
}
public override string GetKey(Platform record) => $"{nameof(Platform)}/{record.Name}";
public override async Task<bool> CanImportAsync(Platform record)
{
var existing = await platformService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Platform> AddAsync(Platform record)
{
var platform = new Data.Models.Platform
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await platformService.AddAsync(platform);
}
catch (Exception ex)
{
throw new ImportSkippedException<Platform>(record, "An unknown error occurred while trying to add platform", ex);
}
}
public override async Task<Data.Models.Platform> UpdateAsync(Platform record)
{
var existing = await platformService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Platform>(record, "An unknown error occurred while trying to update platform", ex);
}
}
public override async Task<bool> ExistsAsync(Platform record) => await platformService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class PublisherImporter(
CompanyService companyService,
ILogger<PublisherImporter> logger) : BaseImporter<Company, Data.Models.Company>
{
public override async Task<ImportItemInfo<Company>> GetImportInfoAsync(Company record)
{
return new ImportItemInfo<Company>
{
Key = GetKey(record),
Name = record.Name,
Type = "Publisher",
Record = record,
};
}
public override string GetKey(Company record) => $"{nameof(Company)}/{record.Name}";
public override async Task<bool> CanImportAsync(Company record)
{
var existing = await companyService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Company> AddAsync(Company record)
{
var company = new Data.Models.Company
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow,
};
try
{
return await companyService.AddAsync(company);
}
catch (Exception ex)
{
throw new ImportSkippedException<Company>(record, "An unknown error occurred while trying to add company", ex);
}
}
public override async Task<Data.Models.Company> UpdateAsync(Company record)
{
var existing = await companyService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Company>(record, "An unknown error occurred while trying to update company", ex);
}
}
public override async Task<bool> ExistsAsync(Company record) => await companyService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -0,0 +1,74 @@
using LANCommander.Launcher.Services.Exceptions;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.Logging;
namespace LANCommander.Launcher.Services.Import.Importers;
public class TagImporter(
TagService tagService,
ILogger<TagImporter> logger) : BaseImporter<Tag, Data.Models.Tag>
{
public override async Task<ImportItemInfo<Tag>> GetImportInfoAsync(Tag record)
{
return new ImportItemInfo<Tag>
{
Key = GetKey(record),
Name = record.Name,
Type = nameof(Tag),
Record = record,
};
}
public override string GetKey(Tag record) => $"{nameof(Tag)}/{record.Name}";
public override async Task<bool> CanImportAsync(Tag record)
{
var existing = await tagService.FirstOrDefaultAsync(c => c.Name == record.Name);
if (existing == null)
return true;
return record.UpdatedOn > existing.ImportedOn;
}
public override async Task<Data.Models.Tag> AddAsync(Tag record)
{
var tag = new Data.Models.Tag
{
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
ImportedOn = DateTime.UtcNow
};
try
{
return await tagService.AddAsync(tag);
}
catch (Exception ex)
{
throw new ImportSkippedException<Tag>(record, "An unknown error occurred while trying to add tag", ex);
}
}
public override async Task<Data.Models.Tag> UpdateAsync(Tag record)
{
var existing = await tagService.FirstOrDefaultAsync(c => c.Name == record.Name);
try
{
existing.Name = record.Name;
existing.CreatedOn = record.CreatedOn;
existing.UpdatedOn = record.UpdatedOn;
existing.ImportedOn = DateTime.UtcNow;
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Tag>(record, "An unknown error occurred while trying to update tag", ex);
}
}
public override async Task<bool> ExistsAsync(Tag record) => await tagService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -1,48 +1,31 @@
using LANCommander.Launcher.Data;
using LANCommander.Launcher.Models;
using LANCommander.SDK.Extensions;
using LANCommander.SDK.Helpers;
using Microsoft.EntityFrameworkCore;
using LANCommander.Launcher.Models;
using Microsoft.Extensions.Logging;
using LANCommander.Launcher.Services.Extensions;
using LANCommander.SDK;
using LANCommander.SDK.Models;
using BaseModel = LANCommander.Launcher.Data.Models.BaseModel;
using LANCommander.Launcher.Services.Import;
using LANCommander.Launcher.Services.Import.Factories;
using LANCommander.SDK.Services;
using Collection = LANCommander.Launcher.Data.Models.Collection;
using Company = LANCommander.Launcher.Data.Models.Company;
using Engine = LANCommander.Launcher.Data.Models.Engine;
using Game = LANCommander.Launcher.Data.Models.Game;
using Genre = LANCommander.Launcher.Data.Models.Genre;
using Media = LANCommander.Launcher.Data.Models.Media;
using MultiplayerMode = LANCommander.Launcher.Data.Models.MultiplayerMode;
using Platform = LANCommander.Launcher.Data.Models.Platform;
using PlaySession = LANCommander.Launcher.Data.Models.PlaySession;
using Settings = LANCommander.Launcher.Settings.Settings;
using Tag = LANCommander.Launcher.Data.Models.Tag;
namespace LANCommander.Launcher.Services
{
public class ImportService(
ILogger<ImportService> logger,
DatabaseContext databaseContext,
LibraryService libraryService,
MediaService mediaService,
EngineService engineService,
GameService gameService,
MessageBusService messageBusService,
SDK.Client client) : BaseService(logger)
ImportContextFactory importContextFactory,
GameClient gameClient,
LibraryClient libraryClient) : BaseService(logger)
{
private ImportProgress _importProgress = new();
public ImportProgress Progress => _importProgress;
public delegate Task OnImportUpdatedHandler(ImportStatusUpdate update);
public event OnImportUpdatedHandler OnImportUpdated;
public delegate Task OnImportCompleteHandler();
public event OnImportCompleteHandler OnImportComplete;
public delegate Task OnImportFailedHandler(Exception ex);
public event OnImportFailedHandler OnImportFailed;
public EventHandler OnImportStarted;
public EventHandler<ImportStatusUpdate> OnImportStatusUpdate;
public EventHandler OnImportComplete;
public EventHandler<Exception> OnImportError;
private IEnumerable<Collection> Collections;
private IEnumerable<Company> Companies;
@ -52,633 +35,45 @@ namespace LANCommander.Launcher.Services
private IEnumerable<Tag> Tags;
private IEnumerable<MultiplayerMode> MultiplayerModes;
public void ImportHasCompleted()
{
OnImportComplete?.Invoke();
}
public async Task ImportAsync()
{
try
{
await ImportLibraryAsync();
OnImportComplete?.Invoke();
OnImportComplete?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OnImportFailed?.Invoke(ex);
}
}
public async Task<Game> ImportGameAsync(Guid id)
{
var game = await client.Games.GetAsync(id);
return await ImportGameAsync(game);
}
public async Task<Game> ImportGameAsync(SDK.Models.Game game)
{
using (var op = Logger.BeginOperation("Importing game {GameTitle}", game.Title))
{
try
{
var existing = false;
var localGame = await gameService.GetAsync(game.Id);
if (localGame == null)
localGame = new Game()
{
Id = game.Id,
};
else
existing = true;
if (localGame.ImportedOn > game.UpdatedOn)
{
Logger.LogInformation($"Skipping {game.Title}, no new information to grab");
return localGame;
}
localGame.Title = game.Title;
localGame.SortTitle = String.IsNullOrWhiteSpace(game.SortTitle) ? String.Empty : game.SortTitle;
localGame.Description = String.IsNullOrWhiteSpace(game.Description) ? String.Empty : game.Description;
localGame.Notes = String.IsNullOrWhiteSpace(game.Notes) ? String.Empty : game.Notes;
localGame.ReleasedOn = game.ReleasedOn;
localGame.ImportedOn = DateTime.UtcNow;
localGame.Type = (Data.Enums.GameType)(int)game.Type;
localGame.Singleplayer = game.Singleplayer;
if (!existing)
localGame = await gameService.AddAsync(localGame);
if (game.BaseGameId != Guid.Empty && localGame.BaseGameId != game.BaseGameId)
{
var baseGame = await gameService.GetAsync(game.BaseGameId);
if (baseGame == null)
{
await ImportGameAsync(game.BaseGameId);
localGame.BaseGame = await gameService.GetAsync(game.BaseGameId);
}
else
{
localGame.BaseGame = baseGame;
}
}
var childGamesIds = game.DependentGames.ToArray();
foreach (var childGameId in childGamesIds)
{
await ImportGameAsync(childGameId);
}
#region Update Game Engine
if (game.Engine == null && localGame.Engine != null)
{
localGame.Engine = null;
localGame.EngineId = null;
}
else if (game.Engine != null)
{
var engine = await engineService.GetAsync(game.Engine.Id);
if (engine != null)
{
localGame.Engine = engine;
localGame.EngineId = engine.Id;
}
}
#endregion
await databaseContext.BulkImport<Collection, SDK.Models.Collection>()
.SetTarget(localGame.Collections)
.UseSource(game.Collections)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.Games)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.Games.Add(localGame);
})
.ImportAsync();
await databaseContext.BulkImport<Genre, SDK.Models.Genre>()
.SetTarget(localGame.Genres)
.UseSource(game.Genres)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.Games)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.Games.Add(localGame);
})
.ImportAsync();
await databaseContext.BulkImport<Company, SDK.Models.Company>()
.SetTarget(localGame.Publishers)
.UseSource(game.Publishers)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.PublishedGames)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.PublishedGames.Add(localGame);
})
.ImportAsync();
await databaseContext.BulkImport<Company, SDK.Models.Company>()
.SetTarget(localGame.Developers)
.UseSource(game.Developers)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.DevelopedGames)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.DevelopedGames.Add(localGame);
})
.ImportAsync();
await databaseContext.BulkImport<Tag, SDK.Models.Tag>()
.SetTarget(localGame.Tags)
.UseSource(game.Tags)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.Games)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.Games.Add(localGame);
})
.ImportAsync();
await databaseContext.BulkImport<MultiplayerMode, SDK.Models.MultiplayerMode>()
.SetTarget(localGame.MultiplayerModes)
.UseSource(game.MultiplayerModes)
.AsBatch()
.Assign((t, s) =>
{
t.Game = localGame;
t.Description = s.Description;
t.MinPlayers = s.MinPlayers;
t.MaxPlayers = s.MaxPlayers;
t.Spectators = s.Spectators;
t.Type = s.Type;
t.NetworkProtocol = s.NetworkProtocol;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
var linked = t.GameId == localGame.Id;
if (!linked)
t.Game = localGame;
})
.ImportAsync();
await databaseContext.BulkImport<Platform, SDK.Models.Platform>()
.SetTarget(localGame.Platforms)
.UseSource(game.Platforms)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
t.Games.Add(localGame);
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
// Check if the relationship already exists
var linked = databaseContext.Entry(t)
.Collection(x => x.Games)
.Query()
.Any(g => g.Id == localGame.Id);
if (!linked)
t.Games.Add(localGame);
})
.ImportAsync();
/*await databaseContext.BulkImport<PlaySession, SDK.Models.PlaySession>()
.SetTarget(localGame.PlaySessions)
.UseSource(game.PlaySessions)
.Include(p => p.Game)
.Assign((t, s) =>
{
t.Game = localGame;
t.Start = s.Start;
t.End = s.End;
t.UserId = s.UserId;
})
.AsNoRemove()
.ImportAsync();*/
var importedMedia = await databaseContext.BulkImport<Media, SDK.Models.Media>()
.SetTarget(localGame.Media)
.UseSource(game.Media)
.AsBatch()
.Assign((t, s) =>
{
t.Name = s.Name;
t.Crc32 = s.Crc32;
t.Type = s.Type;
t.FileId = s.FileId;
t.MimeType = s.MimeType;
t.SourceUrl = s.SourceUrl;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
var linked = t.GameId == localGame.Id;
if (!linked)
t.Game = localGame;
})
.AsNoRemove()
.ImportAsync();
foreach (var media in importedMedia)
{
var localPath = client.Media.GetLocalPath(media.FileId, media.Crc32);
if (!File.Exists(localPath) && media.Type != SDK.Enums.MediaType.Manual)
{
await client.Media.DownloadAsync(new SDK.Models.Media
{
Id = media.Id,
FileId = media.FileId
}, localPath);
messageBusService.MediaChanged(media);
}
}
#region Check Installation Status
foreach (var installDirectory in client.Settings.CurrentValue.Games.InstallDirectories)
{
var gameDirectory = await client.Games.GetInstallDirectory(game, installDirectory);
if (Directory.Exists(gameDirectory))
{
var manifestLocation = ManifestHelper.GetPath(gameDirectory, game.Id);
if (File.Exists(manifestLocation))
{
var manifest = await ManifestHelper.ReadAsync<SDK.GameManifest>(gameDirectory, game.Id);
localGame.Installed = true;
localGame.InstalledOn = DateTime.Now;
localGame.InstallDirectory = gameDirectory;
localGame.InstalledVersion = manifest.Version;
}
}
}
#endregion
var playSessions = await client.PlaySessions.GetAsync(localGame.Id);
if (playSessions != null)
{
await databaseContext.BulkImport<PlaySession, SDK.Models.PlaySession>()
.SetTarget(localGame.PlaySessions)
.UseSource(playSessions)
.Include(p => p.Game)
.Assign((t, s) =>
{
t.Start = s.Start;
t.End = s.End;
t.UserId = s.UserId;
})
.AssignRelationships((t, s) =>
{
// Ensure the game is tracked before checking relationships
if (databaseContext.Entry(localGame).State == EntityState.Detached)
databaseContext.Attach(localGame);
var linked = t.GameId == localGame.Id;
if (!linked)
t.Game = localGame;
})
.AsNoRemove()
.ImportAsync();
}
// Save all pending changes from batch operations
await databaseContext.SaveChangesAsync();
localGame = await gameService.UpdateAsync(localGame);
return localGame;
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not import game {GameTitle}", game.Title);
return null;
}
finally
{
op.Complete();
}
OnImportError?.Invoke(this, ex);
}
}
public async Task ImportLibraryAsync()
{
var remoteLibrary = await client.Library.GetAsync();
var remoteLibrary = await libraryClient.GetAsync();
try
{
await ImportLibraryAsync(remoteLibrary);
}
catch (Exception ex)
{
Logger.LogError(ex, "Could not import library!");
OnImportFailed?.Invoke(ex);
}
}
public async Task ImportLibraryAsync(IEnumerable<SDK.Models.EntityReference> games)
{
Logger?.LogInformation("Starting library import");
_importProgress.IsImporting = true;
_importProgress.Index = 1;
_importProgress.Total = games.Count();
var importContext = importContextFactory.Create();
importContext.OnImportProgress += OnImportStatusUpdate;
foreach (var game in games)
foreach (var game in remoteLibrary)
{
using var transaction = await databaseContext.Database.BeginTransactionAsync();
try
{
var remoteGame = await client.Games.GetAsync(game.Id);
var manifest = await gameClient.GetManifestAsync(game.Id);
_importProgress.CurrentItem = new ImportItem(game.Id, game.Name);
if (OnImportUpdated != null)
{
await OnImportUpdated.Invoke(new ImportStatusUpdate
{
Index = _importProgress.Index,
Total = _importProgress.Total,
CurrentItem = _importProgress.CurrentItem
});
}
if (game != null && remoteGame != null)
{
var importedGame = await ImportGameAsync(remoteGame);
await libraryService.AddToLibraryAsync(importedGame);
}
await databaseContext.SaveChangesAsync();
await transaction.CommitAsync();
await importContext.AddAsync(manifest);
}
catch (Exception ex)
{
await transaction.RollbackAsync();
Logger?.LogError(ex, "Could not import game {GameTitle}", game.Name);
}
finally
{
_importProgress.Index++;
Logger?.LogError(ex, "Could not add game with ID {GameId} to import queue", game.Id);
}
}
_importProgress.IsImporting = false;
if (OnImportComplete != null)
await OnImportComplete.Invoke();
Logger?.LogInformation("Importing games completed");
await importContext.ImportQueueAsync();
}
public async Task ImportGamesAsync(params Guid[] ids)
{
var games = await client.Library.GetAsync();
var toImport = new List<EntityReference>();
foreach (var id in ids)
{
var libraryGame = games.FirstOrDefault(g => g.Id == id);
if (libraryGame == null)
{
Logger?.LogInformation("Game with ID {GameId} not currently in library", id);
continue;
}
toImport.Add(libraryGame);
}
await ImportLibraryAsync(toImport);
}
public async Task ImportRedistributables()
{
}
public async Task<Media> ImportMediaAsync(Guid importMediaId, Guid? gameId = null)
{
SDK.Models.Game game = null;
if (gameId.HasValue)
game = await client.Games.GetAsync(gameId.Value);
var media = await client.Media.GetAsync(importMediaId);
return await ImportMediaAsync(media, game);
}
public async Task<Media> ImportMediaAsync(SDK.Models.Media importMedia, SDK.Models.Game game = null)
{
var media = await mediaService.GetAsync(importMedia.Id);
if (media == null)
media = new Media();
media.FileId = importMedia.FileId;
media.Type = importMedia.Type;
media.SourceUrl = importMedia.SourceUrl;
media.MimeType = importMedia.MimeType;
media.Crc32 = importMedia.Crc32;
media.Name = importMedia.Name ?? String.Empty;
media.GameId = game?.Id ?? Guid.Empty;
if (media.Id == Guid.Empty)
{
media.Id = importMedia.Id;
await mediaService.AddAsync(media);
}
else
await mediaService.UpdateAsync(media);
var localPath = client.Media.GetLocalPath(media.FileId, media.Crc32);
if (!File.Exists(localPath) && media.Type != SDK.Enums.MediaType.Manual)
{
await client.Media.DownloadAsync(new SDK.Models.Media
{
Id = media.Id,
FileId = media.FileId
}, localPath);
messageBusService.MediaChanged(media);
}
return media;
}
// Could use something like automapper, but that's slow.
public async Task<IEnumerable<T>> ImportBulk<T, U, V>(IEnumerable<U> importModels, V service, Func<T, U, T> additionalMapping, bool clean = true)
where T : BaseModel
where U : SDK.Models.KeyedModel
where V : BaseDatabaseService<T>
{
// This could be handled better... DI?
var models = await service.GetAsync();
foreach (var importModel in importModels)
{
using (var transaction = databaseContext.Database.BeginTransaction())
{
try
{
var model = models.FirstOrDefault(m => m.Id == importModel.Id);
if (model == null)
model = (T)Activator.CreateInstance(typeof(T));
model = additionalMapping.Invoke(model, importModel);
if (model.Id == Guid.Empty)
{
model.Id = importModel.Id;
model = await service.AddAsync(model);
}
else
model = await service.UpdateAsync(model);
transaction.Commit();
}
catch (DbUpdateException ex)
{
transaction.Rollback();
continue;
}
catch (Exception ex)
{
}
}
}
if (clean)
{
foreach (var model in models.Where(m => !importModels.Any(im => im.Id == m.Id)))
{
try
{
await service.DeleteAsync(model);
}
catch
{
}
}
}
// Too slow?
return await service.GetAsync();
}
}
}

View file

@ -10,6 +10,7 @@ using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using LANCommander.Launcher.Settings.Enums;
using LANCommander.SDK.Enums;
namespace LANCommander.Launcher.Models
{
@ -114,7 +115,7 @@ namespace LANCommander.Launcher.Models
if (SelectedOptions.Installed)
items = items.Where(i => (i.DataItem as Game).Installed);
items = items.Where(i => (i.DataItem as Game).Type.ValueIsIn(Data.Enums.GameType.MainGame, Data.Enums.GameType.StandaloneExpansion, Data.Enums.GameType.StandaloneMod));
items = items.Where(i => (i.DataItem as Game).Type.ValueIsIn(GameType.MainGame, GameType.StandaloneExpansion, GameType.StandaloneMod));
switch (SelectedOptions.SortBy)
{

View file

@ -31,7 +31,7 @@ public class ElevatedScriptInterceptor : IScriptInterceptor
if (script.RunAsAdmin && !isElevated)
{
var manifest = script.Variables.GetValue<GameManifest>("GameManifest");
var manifest = script.Variables.GetValue<SDK.Models.Manifest.Game>("GameManifest");
var options = new RunScriptCommandLineOptions
{

View file

@ -26,15 +26,18 @@ namespace LANCommander.Launcher.Services
return await Context.Set<T>().FindAsync(id);
}
public virtual async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
{
return await Context.Set<T>().FirstOrDefaultAsync(predicate);
}
public virtual IQueryable<T> Query(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().Where(predicate);
}
public virtual bool Exists(Guid id)
{
return GetAsync(id) != null;
}
public virtual async Task<bool> ExistsAsync(Guid id) => await Context.Set<T>().AnyAsync(x => x.Id == id);
public virtual async Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate) => await Context.Set<T>().AnyAsync(predicate);
public virtual async Task<T> AddAsync(T entity)
{

View file

@ -4,14 +4,14 @@
@inherits FeedbackComponent<ActionSelectorDialogOptions, Process>
@inject GameService GameService
<AntList DataSource="Options.Actions" TItem="SDK.Models.Action" Class="ant-list-clickable" Size="ListSize.Small">
<AntList DataSource="Options.Actions" TItem="SDK.Models.Manifest.Action" Class="ant-list-clickable" Size="ListSize.Small">
<ListItem OnClick="() => Run(Options.Game, context)">
@context.Name
</ListItem>
</AntList>
@code {
void Run(Game game, SDK.Models.Action action)
void Run(Game game, SDK.Models.Manifest.Action action)
{
GameService.Run(game, action);

View file

@ -1,4 +1,5 @@
@using LANCommander.Launcher.Models
@using LANCommander.Launcher.Services.Import
@implements IDisposable
@inject ImportManagerService ImportManagerService
@inject ImportService ImportService
@ -29,9 +30,9 @@
if (ImportService != null)
{
// Clear any existing handlers
ImportService.OnImportComplete -= OnImportCompleteHandler;
ImportService.OnImportFailed -= OnImportFailedHandler;
ImportService.OnImportUpdated -= OnImportUpdatedHandler;
//ImportService.OnImportComplete -= OnImportCompleteHandler;
//ImportService.OnImportFailed -= OnImportFailedHandler;
//ImportService.OnImportUpdated -= OnImportUpdatedHandler;
}
}
@ -42,14 +43,14 @@
Logger?.LogInformation("Starting import process");
// Clear any existing handlers
ImportService.OnImportComplete -= OnImportCompleteHandler;
ImportService.OnImportFailed -= OnImportFailedHandler;
ImportService.OnImportUpdated -= OnImportUpdatedHandler;
//ImportService.OnImportComplete -= OnImportCompleteHandler;
//ImportService.OnImportFailed -= OnImportFailedHandler;
//ImportService.OnImportUpdated -= OnImportUpdatedHandler;
// Add our handlers
ImportService.OnImportComplete += OnImportCompleteHandler;
ImportService.OnImportFailed += OnImportFailedHandler;
ImportService.OnImportUpdated += OnImportUpdatedHandler;
//ImportService.OnImportComplete += OnImportCompleteHandler;
//ImportService.OnImportFailed += OnImportFailedHandler;
//ImportService.OnImportUpdated += OnImportUpdatedHandler;
// Now start the import process
Importing = true;

View file

@ -1,6 +1,7 @@
@using LANCommander.Launcher.Data.Models
@using LANCommander.Launcher.Settings
@using LANCommander.SDK
@using LANCommander.SDK.Enums
@using LANCommander.SDK.Helpers
@using LANCommander.SDK.Services
@using Microsoft.Extensions.Options
@ -48,7 +49,7 @@
@LocalizationService.GetString("ManageSaves")
</MenuItem>
if (Settings.Value.Games.InstallDirectories.Length > 1 || Game.DependentGames.Any(g => g.Type == Data.Enums.GameType.Expansion || g.Type == Data.Enums.GameType.Mod))
if (Settings.Value.Games.InstallDirectories.Length > 1 || Game.DependentGames.Any(g => g.Type == GameType.Expansion || g.Type == GameType.Mod))
{
<MenuItem OnClick="() => Modify()" Disabled="@ConnectionClient.IsOfflineMode()">
@LocalizationService.GetString("Modify")
@ -113,18 +114,18 @@ else
[Parameter] public bool ShowPrimaryActions { get; set; }
Data.Models.Game Game { get; set; }
IEnumerable<SDK.Models.Action> GameActions { get; set; } = [];
IEnumerable<SDK.Models.Manifest.Action> GameActions { get; set; } = [];
protected override async Task OnParametersSetAsync()
{
if (Model.DataItem is Game game)
{
Game = game;
List<SDK.Models.Action> actions = [];
List<SDK.Models.Manifest.Action> actions = [];
if (Game.Installed)
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(Game.InstallDirectory, Game.Id);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(Game.InstallDirectory, Game.Id);
if (manifest != null)
{
@ -137,7 +138,7 @@ else
}
}
async Task Run(Game game, SDK.Models.Action action)
async Task Run(Game game, SDK.Models.Manifest.Action action)
{
var task = GameService.Run(game, action);
@ -162,7 +163,7 @@ else
async Task Install()
{
if (Settings.Value.Games.InstallDirectories.Length > 1 || (Model.DataItem as Game).DependentGames.Any(g => g.Type == Data.Enums.GameType.Expansion || g.Type == Data.Enums.GameType.Mod))
if (Settings.Value.Games.InstallDirectories.Length > 1 || (Model.DataItem as Game).DependentGames.Any(g => g.Type == GameType.Expansion || g.Type == GameType.Mod))
{
var modalOptions = new ModalOptions()
{
@ -221,7 +222,7 @@ else
async Task Uninstall()
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(Game.InstallDirectory, Game.Id);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(Game.InstallDirectory, Game.Id);
string message;
if (manifest.SavePaths != null && manifest.SavePaths.Any())
@ -254,7 +255,7 @@ else
async Task RemoveFromLibrary()
{
var hasStandaloneDependent = Game.DependentGames?
.Where(g => g.Type != Data.Enums.GameType.Expansion && g.Type != Data.Enums.GameType.Mod)
.Where(g => g.Type != GameType.Expansion && g.Type != GameType.Mod)
.Any(g => LibraryService.IsInLibrary(g.Id)) ?? false;
if (!hasStandaloneDependent)

View file

@ -30,8 +30,8 @@
try {
var media = await MediaService.GetAsync(Id);
if (media == null && (Connected || OfflineMode))
media = await ImportService.ImportMediaAsync(Id);
//if (media == null && (Connected || OfflineMode))
// media = await ImportService.ImportMediaAsync(Id);
UpdateMediaUrl(media);
}

View file

@ -2,14 +2,14 @@
@using LANCommander.Launcher.Data.Models
@using LANCommander.Launcher.Models
@using System.Diagnostics
@using LANCommander.SDK.Helpers
@inject SDK.Client Client
@using LANCommander.SDK
@using LANCommander.SDK.Services
@inject GameService GameService
@inject LibraryService LibraryService
@inject InstallService InstallService
@inject GameClient GameClient
@inject LobbyClient LobbyClient
@inject ModalService ModalService
@inject MessageService MessageService
@inject MessageBusService MessageBusService
@inject LocalizationService LocalizationService
@if (State == PlayButtonState.Running)
@ -78,11 +78,11 @@ else
async Task UpdateState()
{
if (Client.Games.IsRunning(LibraryItem.Key))
if (GameClient.IsRunning(LibraryItem.Key))
{
await ChangeState(PlayButtonState.Running);
}
else if (State == PlayButtonState.Running && !Client.Games.IsRunning(LibraryItem.Key))
else if (State == PlayButtonState.Running && !GameClient.IsRunning(LibraryItem.Key))
{
await ChangeState(PlayButtonState.Idle);
}
@ -98,7 +98,7 @@ else
{
var game = LibraryItem.DataItem as Game;
var actions = await Client.Games.GetActionsAsync(game.InstallDirectory, game.Id);
var actions = await GameClient.GetActionsAsync(game.InstallDirectory, game.Id);
await ChangeState(PlayButtonState.Starting);
@ -110,7 +110,7 @@ else
return;
}
var primaryActions = actions.Where(a => a.IsPrimaryAction && (LibraryService.IsInstalled(a.GameId) || a.GameId == Guid.Empty));
var primaryActions = actions.Where(a => a.IsPrimaryAction && LibraryService.IsInstalled(game.Id));
if (primaryActions.Count() == 0)
{
@ -142,7 +142,7 @@ else
WrapClassName = "game-actions-dialog",
AfterClose = async () =>
{
Client.Lobbies.ReleaseSteam();
LobbyClient.ReleaseSteam();
await ChangeState(PlayButtonState.Idle);
}
@ -162,7 +162,7 @@ else
{
await ChangeState(PlayButtonState.Stopping);
await Client.Games.Stop(LibraryItem.Key);
await GameClient.Stop(LibraryItem.Key);
await ChangeState(PlayButtonState.Idle);
}

View file

@ -1,5 +1,6 @@
@using LANCommander.Launcher.Data.Models
@using LANCommander.Launcher.Models
@using LANCommander.SDK.Enums
@using LANCommander.SDK.Extensions
@inherits FeedbackComponent<Models.ListItem, string>
@inject LibraryService LibraryService
@ -27,7 +28,7 @@
IconSelected="@IconType.Outline.Close"
IconUnselected="@IconType.Fill.Pushpin"
ButtonPropsSelected="@(new[] { ("Danger", (object)true) })"
DisableSelector="g => g.Type == Data.Enums.GameType.Mod || g.Type == Data.Enums.GameType.Expansion"
DisableSelector="g => g.Type == GameType.Mod || g.Type == GameType.Expansion"
/>
}
@ -51,7 +52,7 @@
Game = (Game)Options.DataItem;
DependentGames = Game?.DependentGames.OrderBy(g => g.SortTitle ?? g.Title).ToList() ?? [];
SelectedDependentGames = DependentGames?.Where(g => g.Type == Data.Enums.GameType.Expansion || g.Type == Data.Enums.GameType.Mod) ?? [];
SelectedDependentGames = DependentGames?.Where(g => g.Type == GameType.Expansion || g.Type == GameType.Mod) ?? [];
}
async Task Close()

View file

@ -1,5 +1,6 @@
@using LANCommander.Launcher.Data.Models
@using LANCommander.Launcher.Models
@using LANCommander.SDK.Enums
@using LANCommander.SDK.Extensions
@namespace LANCommander.Launcher.UI.Components
@inject LibraryService LibraryService
@ -232,7 +233,7 @@
await Hide();
if (Client.Settings.CurrentValue.Games.InstallDirectories.Length > 1 || (game?.DependentGames?.Any(g => g.Type.IsIn(Data.Enums.GameType.Expansion, Data.Enums.GameType.Mod)) ?? false))
if (Client.Settings.CurrentValue.Games.InstallDirectories.Length > 1 || (game?.DependentGames?.Any(g => g.Type.IsIn(GameType.Expansion, GameType.Mod)) ?? false))
{
var modalOptions = new ModalOptions()
{
@ -272,7 +273,7 @@
try
{
await ImportService.ImportGameAsync(SelectedGame.Id);
// await ImportService.ImportGameAsync(SelectedGame.Id);
await LibraryService.AddToLibraryAsync(SelectedGame.Id);
await LibraryService.RefreshItemsAsync();

View file

@ -44,7 +44,7 @@
StateHasChanged();
await Task.Yield();
await ImportService.ImportGameAsync(Item.Key);
// await ImportService.ImportGameAsync(Item.Key);
await LibraryService.AddToLibraryAsync(Item.Key);
await LibraryService.RefreshItemsAsync();

View file

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using LANCommander.SDK.Models;
namespace LANCommander.SDK.Services;
public class GameInstallationArchiveInfo
{
public GameManifest Manifest { get; internal set; }
public Models.Manifest.Game Manifest { get; internal set; }
public List<ArchiveEntry> Entries { get; private set; } = [];
public List<SavePathEntry> SavePaths { get; internal set; }
}
@ -13,5 +14,5 @@ public class GameInstallationArchiveInfo
public class GameInstallationArchiveEntries
{
public GameInstallationArchiveInfo BaseGame { get; internal set; } = new();
public Dictionary<Guid, GameInstallationArchiveInfo> DependentGames { get; private set; } = [];
public Dictionary<Guid, GameInstallationArchiveInfo> Addons { get; private set; } = [];
}

View file

@ -14,7 +14,7 @@ public class GameInstallationFileListEntry
public Guid GameId { get; internal set; }
public GameManifest Manifest { get; internal set; }
public Models.Manifest.Game Manifest { get; internal set; }
public List<FileEntry> Files { get; private set; } = [];
public void Merge(GameInstallationFileListEntry otherInfo)

View file

@ -1,56 +0,0 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models;
using System;
using System.Collections.Generic;
namespace LANCommander.SDK
{
public class GameManifest : IKeyedModel
{
public Guid Id { get; set; }
public string Title { get; set; }
public string SortTitle { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public GameType Type { get; set; }
public DateTime ReleasedOn { get; set; }
public string Engine { get; set; }
public IEnumerable<string> Genre { get; set; }
public IEnumerable<string> Tags { get; set; }
public IEnumerable<string> Platforms { get; set; }
public IEnumerable<string> Publishers { get; set; }
public IEnumerable<string> Developers { get; set; }
public IEnumerable<string> Collections { get; set; }
public string Version { get; set; }
public IEnumerable<Models.Action> Actions { get; set; }
public bool Singleplayer { get; set; }
public MultiplayerInfo LocalMultiplayer { get; set; }
public MultiplayerInfo LanMultiplayer { get; set; }
public MultiplayerInfo OnlineMultiplayer { get; set; }
public IEnumerable<SavePath> SavePaths { get; set; }
public IEnumerable<string> Keys { get; set; }
public IEnumerable<Script> Scripts { get; set; }
public IEnumerable<Media> Media { get; set; }
public IEnumerable<Archive> Archives { get; set; }
public IEnumerable<GameSave> Saves { get; set; }
public IEnumerable<Guid> DependentGames { get; set; }
public IEnumerable<Redistributable> Redistributables { get; set; }
public IEnumerable<GameCustomField> CustomFields { get; set; }
public GameManifest() { }
}
public class MultiplayerInfo
{
public int MinPlayers { get; set; }
public int MaxPlayers { get; set; }
public string Description { get; set; }
public NetworkProtocol NetworkProtocol { get; set; }
}
public class SavePathEntry
{
public string ArchivePath { get; set; }
public string ActualPath { get; set; }
}
}

View file

@ -4,12 +4,13 @@ using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Models.Manifest
{
public class Game : BaseModel
public class Game : BaseModel, IKeyedModel
{
public Guid Id { get; set; }
public long? IGDBId { get; set; }
public string Title { get; set; }
public string SortTitle { get; set; }
public string Version { get; set; }
public string DirectoryName { get; set; }
public string Notes { get; set; }
public string Description { get; set; }

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace LANCommander.SDK.Models.Manifest
{
public class Redistributable : BaseModel
public class Redistributable : BaseModel, IKeyedModel
{
public Guid Id { get; set; }
public string Name { get; set; }

View file

@ -4,7 +4,7 @@ using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Models.Manifest
{
public class Server : BaseModel
public class Server : BaseModel, IKeyedModel
{
public Guid Id { get; set; }
public string Name { get; set; } = "";

View file

@ -0,0 +1,7 @@
namespace LANCommander.SDK.Models;
public class SavePathEntry
{
public string ArchivePath { get; set; }
public string ActualPath { get; set; }
}

View file

@ -6,7 +6,7 @@ using System.Management.Automation;
namespace LANCommander.SDK.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "GameManifest")]
[OutputType(typeof(GameManifest))]
[OutputType(typeof(SDK.Models.Manifest.Game))]
public class GetGameManifestCmdlet : BaseCmdlet
{
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
@ -17,7 +17,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
protected override void ProcessRecord()
{
WriteObject(ManifestHelper.Read<GameManifest>(Path, Id));
WriteObject(ManifestHelper.Read<SDK.Models.Manifest.Game>(Path, Id));
}
}
}

View file

@ -12,7 +12,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets
public string Path { get; set; }
[Parameter(Mandatory = true, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
public GameManifest Manifest { get; set; }
public SDK.Models.Manifest.Game Manifest { get; set; }
protected override void ProcessRecord()
{

View file

@ -148,9 +148,9 @@ namespace LANCommander.SDK
}
}
public async Task ExecuteGameActionAsync(string installDirectory, Guid gameId, Models.Action action, string args = "", CancellationToken cancellationToken = default)
public async Task ExecuteGameActionAsync(string installDirectory, Guid gameId, Models.Manifest.Action action, string args = "", CancellationToken cancellationToken = default)
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<Models.Manifest.Game>(installDirectory, gameId);
if (action == null)
action = manifest.Actions.FirstOrDefault(a => a.IsPrimaryAction);

View file

@ -112,43 +112,43 @@ namespace LANCommander.SDK.Services
.GetAsync<Game>();
}
public async Task<GameManifest> GetManifestAsync(Guid id)
public async Task<Models.Manifest.Game> GetManifestAsync(Guid id)
{
return await apiRequestFactory
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/api/Games/{id}/Manifest")
.GetAsync<GameManifest>();
.GetAsync<Models.Manifest.Game>();
}
public async Task<ICollection<GameManifest>> GetManifestsAsync(string installDirectory, Guid id)
public async Task<ICollection<Models.Manifest.Game>> GetManifestsAsync(string installDirectory, Guid id)
{
var manifests = new List<GameManifest>();
var mainManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, id);
var manifests = new List<Models.Manifest.Game>();
var mainManifest = await ManifestHelper.ReadAsync<Models.Manifest.Game>(installDirectory, id);
if (mainManifest == null)
return manifests;
manifests.Add(mainManifest);
if (mainManifest.DependentGames != null)
if (mainManifest.Addons != null)
{
foreach (var dependentGameId in mainManifest.DependentGames)
foreach (var addon in mainManifest.Addons)
{
try
{
if (ManifestHelper.Exists(installDirectory, dependentGameId))
if (ManifestHelper.Exists(installDirectory, addon.Id))
{
var dependentGameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, dependentGameId);
var addonManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, addon.Id);
if (dependentGameManifest?.Type == GameType.Expansion || dependentGameManifest?.Type == GameType.Mod)
manifests.Add(dependentGameManifest);
if (addonManifest?.Type == GameType.Expansion || addonManifest?.Type == GameType.Mod)
manifests.Add(addon);
}
}
catch (Exception ex)
{
logger?.LogError(ex, $"Could not load manifest from dependent game {dependentGameId}");
logger?.LogError(ex, $"Could not load manifest from dependent game {addon.Id}");
}
}
}
@ -156,9 +156,9 @@ namespace LANCommander.SDK.Services
return manifests;
}
public async Task<IEnumerable<Models.Action>> GetActionsAsync(string installDirectory, Guid id)
public async Task<IEnumerable<Models.Manifest.Action>> GetActionsAsync(string installDirectory, Guid id)
{
var actions = new List<Models.Action>();
var actions = new List<Models.Manifest.Action>();
try
{
@ -170,7 +170,7 @@ namespace LANCommander.SDK.Services
.UseRoute($"/api/Games/{id}/Actions")
.UseAuthenticationToken()
.UseVersioning()
.GetAsync<IEnumerable<Models.Action>>()
.GetAsync<IEnumerable<Models.Manifest.Action>>()
);
}
}
@ -190,7 +190,8 @@ namespace LANCommander.SDK.Services
.ThenBy(a => a.SortOrder)
.ToList();
}
/*
if (manifests.Any(m => m.OnlineMultiplayer != null && m.OnlineMultiplayer.NetworkProtocol == NetworkProtocol.Lobby || m.LanMultiplayer != null && m.LanMultiplayer.NetworkProtocol == NetworkProtocol.Lobby))
{
var primaryAction = actions.Where(a => a.IsPrimaryAction).First();
@ -220,6 +221,7 @@ namespace LANCommander.SDK.Services
logger?.LogError(ex, "Could not get lobbies");
}
}
*/
return actions;
}
@ -387,7 +389,7 @@ namespace LANCommander.SDK.Services
{
var installResult = new InstallResult(installDirectory, gameId);
var gameFileList = installResult.FileList;
GameManifest manifest = null;
SDK.Models.Manifest.Game manifest = null;
if (string.IsNullOrWhiteSpace(installDirectory))
installDirectory = settingsProvider.CurrentValue.Games.InstallDirectories.First();
@ -422,7 +424,7 @@ namespace LANCommander.SDK.Services
try
{
if (ManifestHelper.Exists(destination, game.Id))
manifest = await ManifestHelper.ReadAsync<GameManifest>(destination, game.Id);
manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(destination, game.Id);
}
catch (Exception ex)
{
@ -638,7 +640,7 @@ namespace LANCommander.SDK.Services
var installResult = new InstallResult(installDirectory, gameId);
var gameFileList = installResult.FileList;
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (manifest == null)
{
logger?.LogInformation("Unable to read or find manifest for game with ID {GameId}. Skip uninstallation!", gameId);
@ -649,16 +651,16 @@ namespace LANCommander.SDK.Services
gameFileList.BaseGame.Manifest = manifest;
var baseFileList = gameFileList.BaseGame;
#region Uninstall Dependent Games
if (manifest.DependentGames != null)
#region Uninstall Addons
if (manifest.Addons != null)
{
foreach (var dependentGame in manifest.DependentGames)
foreach (var addon in manifest.Addons)
{
try
{
if (ManifestHelper.Exists(installDirectory, dependentGame))
if (ManifestHelper.Exists(installDirectory, addon.Id))
{
var dependentResult = await UninstallAsync(installDirectory, dependentGame);
var dependentResult = await UninstallAsync(installDirectory, addon.Id);
gameFileList.MergeDependentGames(dependentResult.FileList);
}
}
@ -736,7 +738,7 @@ namespace LANCommander.SDK.Services
var installResult = new InstallResult(installDirectory, baseGameId);
var gameFileList = installResult.FileList;
var baseManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, baseGameId);
var baseManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, baseGameId);
if (baseManifest == null)
{
logger?.LogInformation("Unable to read or find manifest for addon game with ID {GameId}. Skip uninstallation!", baseGameId);
@ -748,19 +750,19 @@ namespace LANCommander.SDK.Services
gameFileList.InstallDirectory = installDirectory;
addonIds ??= [];
foreach (var dependentGame in baseManifest.DependentGames)
foreach (var addon in baseManifest.Addons)
{
if (!addonIds.Contains(dependentGame))
if (!addonIds.Contains(addon.Id))
continue;
try
{
var dependentResult = await UninstallAddonAsync(installDirectory, dependentGame);
gameFileList.MergeBaseAsDependentGame(dependentGame, dependentResult.FileList);
var dependentResult = await UninstallAddonAsync(installDirectory, addon.Id);
gameFileList.MergeBaseAsDependentGame(addon.Id, dependentResult.FileList);
}
catch (Exception ex)
{
logger?.LogWarning(ex, $"Could not uninstall dependent game {dependentGame} of base game {baseGameId}. Assuming it's already uninstalled or never installed...");
logger?.LogWarning(ex, $"Could not uninstall dependent game {addon} of base game {baseGameId}. Assuming it's already uninstalled or never installed...");
}
}
@ -772,7 +774,7 @@ namespace LANCommander.SDK.Services
var installResult = new InstallResult(installDirectory, addonGameId);
var gameFileList = installResult.FileList;
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, addonGameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, addonGameId);
if (manifest != null)
{
@ -914,12 +916,16 @@ namespace LANCommander.SDK.Services
await WriteScriptsAsync(installDirectory, game);
}
private async Task<GameManifest> WriteManifestAsync(string installDirectory, Game game)
private async Task<Models.Manifest.Game> WriteManifestAsync(string installDirectory, Game game)
{
logger?.LogTrace($"Retrieving game manifest for game {game.Title} with id {game.Id}");
GameManifest manifest = await GetManifestAsync(game.Id);
var manifest = await GetManifestAsync(game.Id);
logger?.LogTrace($"Saving Manifest for game {game.Id} into {installDirectory}");
await ManifestHelper.WriteAsync(manifest, installDirectory);
return manifest;
}
@ -1177,30 +1183,30 @@ namespace LANCommander.SDK.Services
_reader?.Cancel();
}
public async Task<ICollection<GameManifest>> ReadManifestsAsync(string installDirectory, Guid gameId)
public async Task<ICollection<SDK.Models.Manifest.Game>> ReadManifestsAsync(string installDirectory, Guid gameId)
{
var manifests = new List<GameManifest>();
var mainManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifests = new List<SDK.Models.Manifest.Game>();
var mainManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (mainManifest == null)
return manifests;
manifests.Add(mainManifest);
if (mainManifest.DependentGames != null)
if (mainManifest.Addons != null)
{
foreach (var dependentGameId in mainManifest.DependentGames)
foreach (var addon in mainManifest.Addons)
{
try
{
var dependentGameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, dependentGameId);
var dependentGameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, addon.Id);
if (dependentGameManifest.Type == GameType.Expansion || dependentGameManifest.Type == GameType.Mod)
manifests.Add(dependentGameManifest);
}
catch (Exception ex)
{
logger?.LogError(ex, "Could not load manifest from dependent game {DependentGameId}", dependentGameId);
logger?.LogError(ex, "Could not load manifest from dependent game {AddonId}", addon.Id);
}
}
}
@ -1220,7 +1226,7 @@ namespace LANCommander.SDK.Services
/// <exception cref="Exception">
/// Thrown if the request to retrieve archive entries encounters an error.
/// </exception>
protected async Task<IEnumerable<ArchiveEntry>> GetGameInstallationArchiveEntries(Guid gameId, GameManifest manifest)
protected async Task<IEnumerable<ArchiveEntry>> GetGameInstallationArchiveEntries(Guid gameId, Models.Manifest.Game manifest)
{
var entries = await apiRequestFactory
.Create()
@ -1266,10 +1272,10 @@ namespace LANCommander.SDK.Services
{
var depEntries = await GetGameInstallationArchiveEntries(gameId, depManifest);
if (!gameArchives.DependentGames.TryGetValue(depManifest.Id, out var depArchiveInfo))
if (!gameArchives.Addons.TryGetValue(depManifest.Id, out var depArchiveInfo))
{
depArchiveInfo = new();
gameArchives.DependentGames.Add(depManifest.Id, depArchiveInfo);
gameArchives.Addons.Add(depManifest.Id, depArchiveInfo);
}
depArchiveInfo.Manifest = depManifest;
@ -1282,7 +1288,7 @@ namespace LANCommander.SDK.Services
return gameArchives;
}
public async Task RunAsync(string installDirectory, Guid gameId, Models.Action action, DateTime? lastRun, string args = "")
public async Task RunAsync(string installDirectory, Guid gameId, Models.Manifest.Action action, DateTime? lastRun, string args = "")
{
var screen = DisplayHelper.GetScreen();
@ -1420,7 +1426,7 @@ namespace LANCommander.SDK.Services
}
}
private async Task UploadSavesAsync(ICollection<GameManifest> manifests, string installDirectory)
private async Task UploadSavesAsync(ICollection<SDK.Models.Manifest.Game> manifests, string installDirectory)
{
if (connectionClient.IsConnected())
{
@ -1526,7 +1532,7 @@ namespace LANCommander.SDK.Services
var manifest = archives?.BaseGame?.Entries;
var entries = archives?.BaseGame?.Entries?.ToList() ?? [];
foreach ((var dependentGameId, var dependentGameInfo) in archives?.DependentGames ?? [])
foreach ((var dependentGameId, var dependentGameInfo) in archives?.Addons ?? [])
{
foreach (var depArchive in dependentGameInfo.Entries ?? [])
{
@ -1545,14 +1551,14 @@ namespace LANCommander.SDK.Services
}
// lookup for dependent games
var lookupEntry = archives?.DependentGames?
var lookupEntry = archives?.Addons?
.SelectMany(dep => dep.Value?.Entries?.Select(entry => new { GameId = (Guid?)dep.Key, ArchiveEntry = entry }) ?? [])
.ToLookup(tentry => tentry.ArchiveEntry, tentry => tentry.GameId) ?? Enumerable.Empty<Guid?>().ToLookup(x => default(ArchiveEntry));
var conflictedEntries = new List<ArchiveValidationConflict>();
var savePathEntries = archives?.BaseGame?.SavePaths.ToList() ?? [];
var depSavePathEntries = archives?.DependentGames?.SelectMany(dep => dep.Value?.SavePaths ?? []).ToList() ?? [];
var depSavePathEntries = archives?.Addons?.SelectMany(dep => dep.Value?.SavePaths ?? []).ToList() ?? [];
savePathEntries.AddRange(depSavePathEntries);
foreach (var entry in entries)
@ -1637,7 +1643,7 @@ namespace LANCommander.SDK.Services
/// <param name="entries">A collection of file paths to download.</param>
public async Task DownloadFilesAsync(string installDirectory, Guid gameId, ICollection<string> entries)
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
await Task.Run(async () =>
{

View file

@ -88,7 +88,7 @@ namespace LANCommander.SDK.Services
public async Task DownloadAsync(string installDirectory, Guid gameId, Guid? saveId = null)
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
string tempFile;
string tempLocation = string.Empty;
@ -144,7 +144,7 @@ namespace LANCommander.SDK.Services
if (!success)
throw new ExtractionException("Could not extract the save archive. Is the file locked?");
manifest = await ManifestHelper.ReadAsync<GameManifest>(tempLocation);
manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(tempLocation);
#region Move files
var tempLocationFilePath = "Files";
@ -240,7 +240,7 @@ namespace LANCommander.SDK.Services
}
}
public async Task<Stream> PackAsync(string installDirectory, GameManifest manifest)
public async Task<Stream> PackAsync(string installDirectory, SDK.Models.Manifest.Game manifest)
{
using (var savePacker = new SavePacker(installDirectory))
{
@ -253,7 +253,7 @@ namespace LANCommander.SDK.Services
}
}
public async Task<GameSave> UploadAsync(Stream stream, GameManifest manifest)
public async Task<GameSave> UploadAsync(Stream stream, SDK.Models.Manifest.Game manifest)
{
return await apiRequestFactory
.Create()
@ -267,7 +267,7 @@ namespace LANCommander.SDK.Services
{
using (var savePacker = new SavePacker(installDirectory))
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (manifest?.SavePaths?.Any() ?? false)
savePacker.AddPaths(manifest.SavePaths);
@ -293,7 +293,7 @@ namespace LANCommander.SDK.Services
.DeleteAsync<bool>();
}
public IEnumerable<SavePathEntry> GetFileSavePathEntries(SavePath savePath, string installDirectory)
public IEnumerable<SavePathEntry> GetFileSavePathEntries(SDK.Models.Manifest.SavePath savePath, string installDirectory)
{
IEnumerable<string> localPaths;

View file

@ -97,8 +97,8 @@ namespace LANCommander.SDK.Services
{
bool result = default;
var gameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var gameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.DetectInstall);
@ -184,7 +184,7 @@ namespace LANCommander.SDK.Services
{
int result = default;
var gameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var gameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.Install);
@ -259,7 +259,7 @@ namespace LANCommander.SDK.Services
try
{
var gameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var gameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.BeforeStart);
@ -339,7 +339,7 @@ namespace LANCommander.SDK.Services
try
{
var gameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var gameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.AfterStop);
@ -419,7 +419,7 @@ namespace LANCommander.SDK.Services
try
{
var gameManifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var gameManifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.NameChange);
@ -512,7 +512,7 @@ namespace LANCommander.SDK.Services
try
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.Install);
using (var op = logger.BeginOperation("Executing install script"))
@ -579,7 +579,7 @@ namespace LANCommander.SDK.Services
try
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.Uninstall);
using (var op = logger.BeginOperation("Executing uninstall script"))
@ -646,7 +646,7 @@ namespace LANCommander.SDK.Services
try
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.BeforeStart);
using (var op = logger.BeginOperation("Executing before start script"))
@ -716,7 +716,7 @@ namespace LANCommander.SDK.Services
try
{
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.AfterStop);
using (var op = logger.BeginOperation("Executing after stop script"))
@ -786,7 +786,7 @@ namespace LANCommander.SDK.Services
try
{
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.NameChange);
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
using (var op = logger.BeginOperation("Executing name change script"))
{
@ -869,7 +869,7 @@ namespace LANCommander.SDK.Services
try
{
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.KeyChange);
var manifest = await ManifestHelper.ReadAsync<GameManifest>(installDirectory, gameId);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
using (var op = logger.BeginOperation("Executing key change script"))
{

View file

@ -33,7 +33,7 @@ public class SavePacker : IDisposable
_archive = ZipArchive.Create();
}
public SavePacker AddPath(SavePath path)
public SavePacker AddPath(SDK.Models.Manifest.SavePath path)
{
switch (path.Type)
{
@ -52,7 +52,7 @@ public class SavePacker : IDisposable
return this;
}
public SavePacker AddPaths(IEnumerable<SavePath> paths)
public SavePacker AddPaths(IEnumerable<SDK.Models.Manifest.SavePath> paths)
{
foreach (var path in paths)
{
@ -62,7 +62,7 @@ public class SavePacker : IDisposable
return this;
}
public SavePacker AddRegistryPath(SavePath registryPath)
public SavePacker AddRegistryPath(SDK.Models.Manifest.SavePath registryPath)
{
if (registryPath.Type != SavePathType.Registry)
return this;
@ -80,7 +80,7 @@ public class SavePacker : IDisposable
return this;
}
public async Task AddManifestAsync(GameManifest manifest)
public async Task AddManifestAsync(Models.Manifest.Game manifest)
{
_archive.AddEntry(ManifestHelper.ManifestFilename, new MemoryStream(Encoding.UTF8.GetBytes(ManifestHelper.Serialize(manifest))));
}
@ -108,7 +108,7 @@ public class SavePacker : IDisposable
return _stream;
}
private void AddEntriesFromPath(SavePath savePath)
private void AddEntriesFromPath(SDK.Models.Manifest.SavePath savePath)
{
if (savePath.IsRegex)
AddEntriesFromFilePathPattern(savePath.Id, savePath.Path, savePath.WorkingDirectory);

View file

@ -8,6 +8,6 @@ public class ImportContextFactory(IServiceProvider serviceProvider)
{
var scope = serviceProvider.CreateScope();
return scope.ServiceProvider.GetService<ImportContext>();
return scope.ServiceProvider.GetRequiredService<ImportContext>();
}
}

View file

@ -1,8 +1,6 @@
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Helpers;
using LANCommander.Server.Models;
using LANCommander.SDK;
using System.IO.Compression;
using System.Linq.Expressions;
using AutoMapper;
@ -14,7 +12,6 @@ using LANCommander.Server.Services.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using SharpCompress.Common;
using PascalCaseNamingConvention = YamlDotNet.Serialization.NamingConventions.PascalCaseNamingConvention;
namespace LANCommander.Server.Services
@ -118,7 +115,7 @@ namespace LANCommander.Server.Services
await base.DeleteAsync(archive);
}
public async Task<GameManifest> ReadManifestAsync(string objectKey)
public async Task<SDK.Models.Manifest.Game> ReadManifestAsync(string objectKey)
{
var upload = await GetArchiveFileLocationAsync(objectKey);
@ -145,7 +142,7 @@ namespace LANCommander.Server.Services
.WithNamingConvention(new PascalCaseNamingConvention())
.Build();
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
var manifest = deserializer.Deserialize<SDK.Models.Manifest.Game>(manifestContents);
return manifest;
}
@ -173,7 +170,7 @@ namespace LANCommander.Server.Services
}
}
public async Task<bool> ExistsAsync(Guid archiveId)
public override async Task<bool> ExistsAsync(Guid archiveId)
{
var archive = await Include(a => a.StorageLocation).GetAsync(archiveId);

View file

@ -118,7 +118,7 @@ namespace LANCommander.Server.Services
return await GetAsync(g => g.AddonTypes.Contains(g.Type));
}
public async Task<GameManifest> GetManifestAsync(Guid id)
public async Task<SDK.Models.Manifest.Game> GetManifestAsync(Guid id)
{
var game = await Query(q =>
{
@ -148,138 +148,12 @@ namespace LANCommander.Server.Services
return GetManifest(game);
}
public GameManifest GetManifest(Game game)
public SDK.Models.Manifest.Game GetManifest(Game game)
{
if (game == null)
return null;
var manifest = new GameManifest()
{
Id = game.Id,
Title = game.Title,
SortTitle = game.SortTitle,
Description = game.Description,
Notes = game.Notes,
ReleasedOn = game.ReleasedOn.GetValueOrDefault(),
Singleplayer = game.Singleplayer,
Type = (SDK.Enums.GameType)(int)game.Type,
};
if (game.Engine != null)
manifest.Engine = game.Engine.Name;
if (game.Genres != null && game.Genres.Count > 0)
manifest.Genre = game.Genres.Select(g => g.Name).ToArray();
if (game.Tags != null && game.Tags.Count > 0)
manifest.Tags = game.Tags.Select(g => g.Name).ToArray();
if (game.Publishers != null && game.Publishers.Count > 0)
manifest.Publishers = game.Publishers.Select(g => g.Name).ToArray();
if (game.Developers != null && game.Developers.Count > 0)
manifest.Developers = game.Developers.Select(g => g.Name).ToArray();
if (game.Collections != null && game.Collections.Count > 0)
manifest.Collections = game.Collections.Select(c => c.Name).ToArray();
if (game.Archives != null && game.Archives.Count > 0)
manifest.Version = game.Archives.OrderByDescending(a => a.CreatedOn).First().Version;
if (game.Media != null && game.Media.Count > 0)
manifest.Media = mapper.Map<IEnumerable<SDK.Models.Media>>(game.Media);
if (game.Actions != null && game.Actions.Count > 0)
{
manifest.Actions = game.Actions.Select(a => new SDK.Models.Action()
{
Name = a.Name,
Arguments = a.Arguments,
Path = a.Path,
WorkingDirectory = a.WorkingDirectory,
IsPrimaryAction = a.PrimaryAction,
SortOrder = a.SortOrder,
}).ToArray();
}
if (game.MultiplayerModes != null && game.MultiplayerModes.Count > 0)
{
var local = game.MultiplayerModes.FirstOrDefault(m => m.Type == MultiplayerType.Local);
var lan = game.MultiplayerModes.FirstOrDefault(m => m.Type == MultiplayerType.LAN);
var online = game.MultiplayerModes.FirstOrDefault(m => m.Type == MultiplayerType.Online);
if (local != null)
manifest.LocalMultiplayer = new MultiplayerInfo()
{
MinPlayers = local.MinPlayers,
MaxPlayers = local.MaxPlayers,
Description = local.Description,
NetworkProtocol = local.NetworkProtocol,
};
if (lan != null)
manifest.LanMultiplayer = new MultiplayerInfo()
{
MinPlayers = lan.MinPlayers,
MaxPlayers = lan.MaxPlayers,
Description = lan.Description,
NetworkProtocol = lan.NetworkProtocol,
};
if (online != null)
manifest.OnlineMultiplayer = new MultiplayerInfo()
{
MinPlayers = online.MinPlayers,
MaxPlayers = online.MaxPlayers,
Description = online.Description,
NetworkProtocol = online.NetworkProtocol,
};
}
if (game.SavePaths != null && game.SavePaths.Count > 0)
{
manifest.SavePaths = game.SavePaths.Select(p => new SDK.Models.SavePath()
{
Id = p.Id,
Path = p.Path,
IsRegex = p.IsRegex,
WorkingDirectory = p.WorkingDirectory,
Type = p.Type
});
}
if (game.Scripts != null && game.Scripts.Count > 0)
{
manifest.Scripts = game.Scripts.Select(p => new SDK.Models.Script()
{
Id = p.Id,
Type = p.Type,
Name = p.Name,
Description = p.Description,
RequiresAdmin = p.RequiresAdmin
});
}
if (game.Redistributables != null && game.Redistributables.Count > 0)
{
manifest.Redistributables = game.Redistributables.Select(p => new SDK.Models.Redistributable()
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
Notes = p.Notes
});
}
if (game.DependentGames != null && game.DependentGames.Count > 0)
{
manifest.DependentGames = game.DependentGames.Select(g => g.Id).ToArray();
}
if (game.CustomFields != null && game.CustomFields.Count > 0)
{
manifest.CustomFields = game.CustomFields.Select(cf => new SDK.Models.GameCustomField(cf.Name, cf.Value)).ToArray();
}
var manifest = mapper.Map<SDK.Models.Manifest.Game>(game);
return manifest;
}

View file

@ -89,7 +89,7 @@ public static class GameEndpoints
[FromServices] IMapper mapper,
Guid id)
{
var game = await cache.GetOrSetAsync<SDK.Models.Game>($"Games/{id}", async _ =>
var game = await cache.GetOrSetAsync<SDK.Models.Manifest.Game>($"Games/{id}", async _ =>
{
var result = await gameService
.Include(g => g.Actions)
@ -112,7 +112,7 @@ public static class GameEndpoints
.AsSplitQuery()
.GetAsync(id);
return mapper.Map<SDK.Models.Game>(result);
return mapper.Map<SDK.Models.Manifest.Game>(result);
}, TimeSpan.MaxValue, tags: ["Games", $"Games/{id}"]);
if (game != null)
@ -127,7 +127,7 @@ public static class GameEndpoints
Guid id)
{
var manifest = await cache
.GetOrSetAsync<SDK.GameManifest>(
.GetOrSetAsync<SDK.Models.Manifest.Game>(
$"Game/{id}/Manifest",
async _ => await gameService.GetManifestAsync(id),
TimeSpan.MaxValue,