diff --git a/LANCommander.Launcher.Data/Enums/GameType.cs b/LANCommander.Launcher.Data/Enums/GameType.cs deleted file mode 100644 index cad57435..00000000 --- a/LANCommander.Launcher.Data/Enums/GameType.cs +++ /dev/null @@ -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 - } -} diff --git a/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.Designer.cs b/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.Designer.cs new file mode 100644 index 00000000..ab1e9299 --- /dev/null +++ b/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.Designer.cs @@ -0,0 +1,852 @@ +// +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 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + modelBuilder.Entity("CategoryGame", b => + { + b.Property("CategoriesId") + .HasColumnType("TEXT"); + + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.HasKey("CategoriesId", "GamesId"); + + b.HasIndex("GamesId"); + + b.ToTable("CategoryGame"); + }); + + modelBuilder.Entity("CollectionGame", b => + { + b.Property("CollectionId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.HasKey("CollectionId", "GameId"); + + b.HasIndex("GameId"); + + b.ToTable("CollectionGame"); + }); + + modelBuilder.Entity("GameDeveloper", b => + { + b.Property("DeveloperId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.HasKey("DeveloperId", "GameId"); + + b.HasIndex("GameId"); + + b.ToTable("GameDeveloper"); + }); + + modelBuilder.Entity("GameGenre", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("GenresId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("GameGenre"); + }); + + modelBuilder.Entity("GamePlatform", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("PlatformsId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "PlatformsId"); + + b.HasIndex("PlatformsId"); + + b.ToTable("GamePlatform"); + }); + + modelBuilder.Entity("GamePublisher", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("PublisherId") + .HasColumnType("TEXT"); + + b.HasKey("GameId", "PublisherId"); + + b.HasIndex("PublisherId"); + + b.ToTable("GamePublisher"); + }); + + modelBuilder.Entity("GameRedistributable", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("RedistributableId") + .HasColumnType("TEXT"); + + b.HasKey("GameId", "RedistributableId"); + + b.HasIndex("RedistributableId"); + + b.ToTable("GameRedistributable"); + }); + + modelBuilder.Entity("GameTag", b => + { + b.Property("GamesId") + .HasColumnType("TEXT"); + + b.Property("TagsId") + .HasColumnType("TEXT"); + + b.HasKey("GamesId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("GameTag"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Collection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Collections"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Company", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Companies"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Engine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Engines"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("BaseGameId") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EngineId") + .HasColumnType("TEXT"); + + b.Property("IGDBId") + .HasColumnType("INTEGER"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("InstallDirectory") + .HasColumnType("TEXT"); + + b.Property("Installed") + .HasColumnType("INTEGER"); + + b.Property("InstalledOn") + .HasColumnType("TEXT"); + + b.Property("InstalledVersion") + .HasColumnType("TEXT"); + + b.Property("LatestVersion") + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("ReleasedOn") + .HasColumnType("TEXT"); + + b.Property("Singleplayer") + .HasColumnType("INTEGER"); + + b.Property("SortTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Media", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Crc32") + .IsRequired() + .HasMaxLength(8) + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("FileId") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("MimeType") + .HasMaxLength(255) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("MinPlayers") + .HasColumnType("INTEGER"); + + b.Property("NetworkProtocol") + .HasColumnType("INTEGER"); + + b.Property("Spectators") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("MultiplayerModes"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Platform", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Platforms"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.PlaySession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("End") + .HasColumnType("TEXT"); + + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Start") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("PlaySessions"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Redistributable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Redistributables"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("LANCommander.Launcher.Data.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("CreatedOn") + .HasColumnType("TEXT"); + + b.Property("ImportedOn") + .HasColumnType("TEXT"); + + b.Property("UpdatedOn") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LibraryGame", b => + { + b.Property("GameId") + .HasColumnType("TEXT"); + + b.Property("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 + } + } +} diff --git a/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.cs b/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.cs new file mode 100644 index 00000000..bad830ab --- /dev/null +++ b/LANCommander.Launcher.Data/Migrations/20251130001620_AddImportedOn.cs @@ -0,0 +1,180 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LANCommander.Launcher.Data.Migrations +{ + /// + public partial class AddImportedOn : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Users", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Tags", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Redistributables", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "PlaySessions", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Platforms", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "MultiplayerModes", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Media", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Libraries", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Genres", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AlterColumn( + 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( + name: "ImportedOn", + table: "Engines", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Companies", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Collections", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "ImportedOn", + table: "Categories", + type: "TEXT", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + /// + 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( + name: "ImportedOn", + table: "Games", + type: "TEXT", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "TEXT"); + } + } +} diff --git a/LANCommander.Launcher.Data/Migrations/DatabaseContextModelSnapshot.cs b/LANCommander.Launcher.Data/Migrations/DatabaseContextModelSnapshot.cs index f80e68d3..efe3f132 100644 --- a/LANCommander.Launcher.Data/Migrations/DatabaseContextModelSnapshot.cs +++ b/LANCommander.Launcher.Data/Migrations/DatabaseContextModelSnapshot.cs @@ -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("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -172,6 +175,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -193,6 +199,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -214,6 +223,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -247,7 +259,7 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("IGDBId") .HasColumnType("INTEGER"); - b.Property("ImportedOn") + b.Property("ImportedOn") .HasColumnType("TEXT"); b.Property("InstallDirectory") @@ -305,6 +317,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -326,6 +341,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("UpdatedOn") .HasColumnType("TEXT"); @@ -360,6 +378,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("GameId") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("MimeType") .HasMaxLength(255) .HasColumnType("TEXT"); @@ -406,6 +427,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("GameId") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("MaxPlayers") .HasColumnType("INTEGER"); @@ -440,6 +464,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -467,6 +494,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("GameId") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Start") .HasColumnType("TEXT"); @@ -495,6 +525,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("Description") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -519,6 +552,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT"); @@ -543,6 +579,9 @@ namespace LANCommander.Launcher.Data.Migrations b.Property("CreatedOn") .HasColumnType("TEXT"); + b.Property("ImportedOn") + .HasColumnType("TEXT"); + b.Property("UpdatedOn") .HasColumnType("TEXT"); diff --git a/LANCommander.Launcher.Data/Models/BaseModel.cs b/LANCommander.Launcher.Data/Models/BaseModel.cs index c1190774..d6d7fe48 100644 --- a/LANCommander.Launcher.Data/Models/BaseModel.cs +++ b/LANCommander.Launcher.Data/Models/BaseModel.cs @@ -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; } diff --git a/LANCommander.Launcher.Data/Models/Game.cs b/LANCommander.Launcher.Data/Models/Game.cs index 3dc5ffec..b5539c78 100644 --- a/LANCommander.Launcher.Data/Models/Game.cs +++ b/LANCommander.Launcher.Data/Models/Game.cs @@ -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; } diff --git a/LANCommander.Launcher.Data/Models/Media.cs b/LANCommander.Launcher.Data/Models/Media.cs index a42bbb82..7a9742e2 100644 --- a/LANCommander.Launcher.Data/Models/Media.cs +++ b/LANCommander.Launcher.Data/Models/Media.cs @@ -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; diff --git a/LANCommander.Launcher.Data/Models/MultiplayerMode.cs b/LANCommander.Launcher.Data/Models/MultiplayerMode.cs index a794ec52..7fc034a6 100644 --- a/LANCommander.Launcher.Data/Models/MultiplayerMode.cs +++ b/LANCommander.Launcher.Data/Models/MultiplayerMode.cs @@ -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; diff --git a/LANCommander.Launcher.Models/ActionSelectorDialogOptions.cs b/LANCommander.Launcher.Models/ActionSelectorDialogOptions.cs index a1e5bbe1..deaf099e 100644 --- a/LANCommander.Launcher.Models/ActionSelectorDialogOptions.cs +++ b/LANCommander.Launcher.Models/ActionSelectorDialogOptions.cs @@ -9,7 +9,7 @@ namespace LANCommander.Launcher.Models { public class ActionSelectorDialogOptions { - public IEnumerable Actions { get; set; } + public IEnumerable Actions { get; set; } public string Title { get; set; } public Game Game { get; set; } } diff --git a/LANCommander.Launcher.Services/CommandLineService.cs b/LANCommander.Launcher.Services/CommandLineService.cs index 6cefa831..d3a75406 100644 --- a/LANCommander.Launcher.Services/CommandLineService.cs +++ b/LANCommander.Launcher.Services/CommandLineService.cs @@ -123,14 +123,14 @@ namespace LANCommander.Launcher.Services try { - var game = await gameService.GetAsync(options.GameId); - var manifest = await ManifestHelper.ReadAsync(game.InstallDirectory, game.Id); + /*var game = await gameService.GetAsync(options.GameId); + var manifest = await ManifestHelper.ReadAsync(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(); } diff --git a/LANCommander.Launcher.Services/Exceptions/ImportSkippedException.cs b/LANCommander.Launcher.Services/Exceptions/ImportSkippedException.cs new file mode 100644 index 00000000..125b3ea5 --- /dev/null +++ b/LANCommander.Launcher.Services/Exceptions/ImportSkippedException.cs @@ -0,0 +1,17 @@ +namespace LANCommander.Launcher.Services.Exceptions; + +public class ImportSkippedException : 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; + } +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs index dd028458..b62d3676 100644 --- a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs +++ b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs @@ -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 configure) { - services.AddDbContext(); + services.AddDbContext(options => + { + options.EnableSensitiveDataLogging(); + }); #region Register Client var options = new LANCommanderOptions(); @@ -55,6 +61,21 @@ namespace LANCommander.Launcher.Services.Extensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + + #region Import + + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + #endregion return services; } diff --git a/LANCommander.Launcher.Services/FilterService.cs b/LANCommander.Launcher.Services/FilterService.cs index c3710bcb..5ac99b9c 100644 --- a/LANCommander.Launcher.Services/FilterService.cs +++ b/LANCommander.Launcher.Services/FilterService.cs @@ -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(); } diff --git a/LANCommander.Launcher.Services/GameService.cs b/LANCommander.Launcher.Services/GameService.cs index 6a82328a..80e13484 100644 --- a/LANCommander.Launcher.Services/GameService.cs +++ b/LANCommander.Launcher.Services/GameService.cs @@ -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; diff --git a/LANCommander.Launcher.Services/Import/Factories/ImportContextFactory.cs b/LANCommander.Launcher.Services/Import/Factories/ImportContextFactory.cs new file mode 100644 index 00000000..c41b94e8 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Factories/ImportContextFactory.cs @@ -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(); + } +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/IImportItemInfo.cs b/LANCommander.Launcher.Services/Import/IImportItemInfo.cs new file mode 100644 index 00000000..347f6385 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/IImportItemInfo.cs @@ -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; } +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/IImporter.cs b/LANCommander.Launcher.Services/Import/IImporter.cs new file mode 100644 index 00000000..91be2030 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/IImporter.cs @@ -0,0 +1,11 @@ +namespace LANCommander.Launcher.Services.Import; + +public interface IImporter where TRecord : class +{ + void UseContext(ImportContext importContext); + Task> GetImportInfoAsync(TRecord record); + Task CanImportAsync(TRecord record); + Task AddAsync(TRecord record); + Task UpdateAsync(TRecord record); + Task ExistsAsync(TRecord record); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/ImportContext.cs b/LANCommander.Launcher.Services/Import/ImportContext.cs new file mode 100644 index 00000000..076a0be0 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/ImportContext.cs @@ -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 OnImportProgress { get; set; } + public EventHandler OnImportCompleted { get; set; } + + private List _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(); + _developers = serviceProvider.GetRequiredService(); + _engines = serviceProvider.GetRequiredService(); + _games = serviceProvider.GetRequiredService(); + _genres = serviceProvider.GetRequiredService(); + _multiplayerModes = serviceProvider.GetRequiredService(); + _platforms = serviceProvider.GetRequiredService(); + _publishers = serviceProvider.GetRequiredService(); + _tags = serviceProvider.GetRequiredService(); + } + + 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 record, BaseImporter 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); + } +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/ImportItemInfo.cs b/LANCommander.Launcher.Services/Import/ImportItemInfo.cs new file mode 100644 index 00000000..3c62f5a0 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/ImportItemInfo.cs @@ -0,0 +1,10 @@ +namespace LANCommander.Launcher.Services.Import; + +public class ImportItemInfo : 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; } +} \ No newline at end of file diff --git a/LANCommander.Launcher.Models/ImportStatusUpdate.cs b/LANCommander.Launcher.Services/Import/ImportStatusUpdate.cs similarity index 50% rename from LANCommander.Launcher.Models/ImportStatusUpdate.cs rename to LANCommander.Launcher.Services/Import/ImportStatusUpdate.cs index 030b2a9a..55ca092a 100644 --- a/LANCommander.Launcher.Models/ImportStatusUpdate.cs +++ b/LANCommander.Launcher.Services/Import/ImportStatusUpdate.cs @@ -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; } } \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/BaseImporter.cs b/LANCommander.Launcher.Services/Import/Importers/BaseImporter.cs new file mode 100644 index 00000000..20f38bf7 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/BaseImporter.cs @@ -0,0 +1,37 @@ +using LANCommander.Launcher.Services.Exceptions; + +namespace LANCommander.Launcher.Services.Import.Importers; + +public abstract class BaseImporter : IImporter where TRecord : class +{ + protected ImportContext ImportContext { get; private set; } + + public void UseContext(ImportContext importContext) + { + ImportContext = importContext; + } + + public async Task ImportAsync(IImportItemInfo importItem) + { + if (importItem is ImportItemInfo 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(null, "Import item record is not supported by this importer."); + } + + public abstract string GetKey(TRecord record); + public abstract Task> GetImportInfoAsync(TRecord record); + public abstract Task CanImportAsync(TRecord record); + public abstract Task AddAsync(TRecord record); + public abstract Task UpdateAsync(TRecord record); + public abstract Task ExistsAsync(TRecord record); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/CollectionImporter.cs b/LANCommander.Launcher.Services/Import/Importers/CollectionImporter.cs new file mode 100644 index 00000000..ad52407d --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/CollectionImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Collection record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add collection", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update collection", ex); + } + } + + public override async Task ExistsAsync(Collection record) => await collectionService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/DeveloperImporter.cs b/LANCommander.Launcher.Services/Import/Importers/DeveloperImporter.cs new file mode 100644 index 00000000..d1504196 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/DeveloperImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Company record) + { + return new ImportItemInfo + { + Key = GetKey(record), + Name = record.Name, + Type = "Developer", + Record = record, + }; + } + + public override string GetKey(Company record) => $"{nameof(Company)}/{record.Name}"; + + public override async Task 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 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(record, "An unknown error occurred while trying to add company", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update company", ex); + } + } + + public override async Task ExistsAsync(Company record) => await companyService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/EngineImporter.cs b/LANCommander.Launcher.Services/Import/Importers/EngineImporter.cs new file mode 100644 index 00000000..6d365f80 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/EngineImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Engine record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add engine", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update engine", ex); + } + } + + public override async Task ExistsAsync(Engine record) => await engineService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/GameImporter.cs b/LANCommander.Launcher.Services/Import/Importers/GameImporter.cs new file mode 100644 index 00000000..23adbf0b --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/GameImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Game record) + { + return new ImportItemInfo + { + 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 CanImportAsync(Game record) + { + var existing = await gameService.GetAsync(record.Id); + + if (existing == null) + return true; + + return record.UpdatedOn > existing.ImportedOn; + } + + public override async Task 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(record, "An unknown error occurred while trying to add game", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update game", ex); + } + } + + public override async Task ExistsAsync(Game record) => await gameService.ExistsAsync(record.Id); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/GenreImporter.cs b/LANCommander.Launcher.Services/Import/Importers/GenreImporter.cs new file mode 100644 index 00000000..d53b3544 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/GenreImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Genre record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add genre", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update genre", ex); + } + } + + public override async Task ExistsAsync(Genre record) => await genreService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/MultiplayerModeImporter.cs b/LANCommander.Launcher.Services/Import/Importers/MultiplayerModeImporter.cs new file mode 100644 index 00000000..e3d50a8e --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/MultiplayerModeImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(MultiplayerMode record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add multiplayer mode", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update multiplayer mode", ex); + } + } + + public override async Task ExistsAsync(MultiplayerMode record) => await multiplayerModeService.ExistsAsync(mm => mm.Type == record.Type && mm.NetworkProtocol == record.NetworkProtocol); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/PlatformImporter.cs b/LANCommander.Launcher.Services/Import/Importers/PlatformImporter.cs new file mode 100644 index 00000000..169cc0af --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/PlatformImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Platform record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add platform", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update platform", ex); + } + } + + public override async Task ExistsAsync(Platform record) => await platformService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/PublisherImporter.cs b/LANCommander.Launcher.Services/Import/Importers/PublisherImporter.cs new file mode 100644 index 00000000..a23f0332 --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/PublisherImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Company record) + { + return new ImportItemInfo + { + Key = GetKey(record), + Name = record.Name, + Type = "Publisher", + Record = record, + }; + } + + public override string GetKey(Company record) => $"{nameof(Company)}/{record.Name}"; + + public override async Task 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 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(record, "An unknown error occurred while trying to add company", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update company", ex); + } + } + + public override async Task ExistsAsync(Company record) => await companyService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/Import/Importers/TagImporter.cs b/LANCommander.Launcher.Services/Import/Importers/TagImporter.cs new file mode 100644 index 00000000..0606e95c --- /dev/null +++ b/LANCommander.Launcher.Services/Import/Importers/TagImporter.cs @@ -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 logger) : BaseImporter +{ + public override async Task> GetImportInfoAsync(Tag record) + { + return new ImportItemInfo + { + 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 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 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(record, "An unknown error occurred while trying to add tag", ex); + } + } + + public override async Task 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(record, "An unknown error occurred while trying to update tag", ex); + } + } + + public override async Task ExistsAsync(Tag record) => await tagService.ExistsAsync(c => c.Name == record.Name); +} \ No newline at end of file diff --git a/LANCommander.Launcher.Services/ImportService.cs b/LANCommander.Launcher.Services/ImportService.cs index 10f91f3a..3ea26ec2 100644 --- a/LANCommander.Launcher.Services/ImportService.cs +++ b/LANCommander.Launcher.Services/ImportService.cs @@ -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 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 OnImportStatusUpdate; + public EventHandler OnImportComplete; + public EventHandler OnImportError; private IEnumerable Collections; private IEnumerable Companies; @@ -52,633 +35,45 @@ namespace LANCommander.Launcher.Services private IEnumerable Tags; private IEnumerable 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 ImportGameAsync(Guid id) - { - var game = await client.Games.GetAsync(id); - - return await ImportGameAsync(game); - } - - public async Task 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() - .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() - .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() - .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() - .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() - .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() - .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() - .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() - .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() - .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(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() - .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 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(); - - 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 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 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> ImportBulk(IEnumerable importModels, V service, Func additionalMapping, bool clean = true) - where T : BaseModel - where U : SDK.Models.KeyedModel - where V : BaseDatabaseService - { - // 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(); - } - } } diff --git a/LANCommander.Launcher.Services/Models/LibraryFilter.cs b/LANCommander.Launcher.Services/Models/LibraryFilter.cs index ffd16e56..d3c01320 100644 --- a/LANCommander.Launcher.Services/Models/LibraryFilter.cs +++ b/LANCommander.Launcher.Services/Models/LibraryFilter.cs @@ -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) { diff --git a/LANCommander.Launcher.Services/PowerShell/ElevatedScriptInterceptor.cs b/LANCommander.Launcher.Services/PowerShell/ElevatedScriptInterceptor.cs index 7c9092dc..8f4bfc40 100644 --- a/LANCommander.Launcher.Services/PowerShell/ElevatedScriptInterceptor.cs +++ b/LANCommander.Launcher.Services/PowerShell/ElevatedScriptInterceptor.cs @@ -31,7 +31,7 @@ public class ElevatedScriptInterceptor : IScriptInterceptor if (script.RunAsAdmin && !isElevated) { - var manifest = script.Variables.GetValue("GameManifest"); + var manifest = script.Variables.GetValue("GameManifest"); var options = new RunScriptCommandLineOptions { diff --git a/LANCommander.Launcher.Services/_BaseDatabaseService.cs b/LANCommander.Launcher.Services/_BaseDatabaseService.cs index c8bc1240..f8f04727 100644 --- a/LANCommander.Launcher.Services/_BaseDatabaseService.cs +++ b/LANCommander.Launcher.Services/_BaseDatabaseService.cs @@ -26,15 +26,18 @@ namespace LANCommander.Launcher.Services return await Context.Set().FindAsync(id); } + public virtual async Task FirstOrDefaultAsync(Expression> predicate) + { + return await Context.Set().FirstOrDefaultAsync(predicate); + } + public virtual IQueryable Query(Expression> predicate) { return Context.Set().Where(predicate); } - public virtual bool Exists(Guid id) - { - return GetAsync(id) != null; - } + public virtual async Task ExistsAsync(Guid id) => await Context.Set().AnyAsync(x => x.Id == id); + public virtual async Task ExistsAsync(Expression> predicate) => await Context.Set().AnyAsync(predicate); public virtual async Task AddAsync(T entity) { diff --git a/LANCommander.Launcher/UI/Components/ActionSelectorDialog.razor b/LANCommander.Launcher/UI/Components/ActionSelectorDialog.razor index bd7d24e0..1ade4d99 100644 --- a/LANCommander.Launcher/UI/Components/ActionSelectorDialog.razor +++ b/LANCommander.Launcher/UI/Components/ActionSelectorDialog.razor @@ -4,14 +4,14 @@ @inherits FeedbackComponent @inject GameService GameService - + @context.Name @code { - void Run(Game game, SDK.Models.Action action) + void Run(Game game, SDK.Models.Manifest.Action action) { GameService.Run(game, action); diff --git a/LANCommander.Launcher/UI/Components/ImportHandler.razor b/LANCommander.Launcher/UI/Components/ImportHandler.razor index 18881af6..13602477 100644 --- a/LANCommander.Launcher/UI/Components/ImportHandler.razor +++ b/LANCommander.Launcher/UI/Components/ImportHandler.razor @@ -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; diff --git a/LANCommander.Launcher/UI/Components/Library/LibraryItemContextMenu.razor b/LANCommander.Launcher/UI/Components/Library/LibraryItemContextMenu.razor index 88de751b..6d714b83 100644 --- a/LANCommander.Launcher/UI/Components/Library/LibraryItemContextMenu.razor +++ b/LANCommander.Launcher/UI/Components/Library/LibraryItemContextMenu.razor @@ -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") - 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)) { @LocalizationService.GetString("Modify") @@ -113,18 +114,18 @@ else [Parameter] public bool ShowPrimaryActions { get; set; } Data.Models.Game Game { get; set; } - IEnumerable GameActions { get; set; } = []; + IEnumerable GameActions { get; set; } = []; protected override async Task OnParametersSetAsync() { if (Model.DataItem is Game game) { Game = game; - List actions = []; + List actions = []; if (Game.Installed) { - var manifest = await ManifestHelper.ReadAsync(Game.InstallDirectory, Game.Id); + var manifest = await ManifestHelper.ReadAsync(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(Game.InstallDirectory, Game.Id); + var manifest = await ManifestHelper.ReadAsync(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) diff --git a/LANCommander.Launcher/UI/Components/MediaImage.razor b/LANCommander.Launcher/UI/Components/MediaImage.razor index b186c108..c94e9cfa 100644 --- a/LANCommander.Launcher/UI/Components/MediaImage.razor +++ b/LANCommander.Launcher/UI/Components/MediaImage.razor @@ -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); } diff --git a/LANCommander.Launcher/UI/Components/PlayButton.razor b/LANCommander.Launcher/UI/Components/PlayButton.razor index 11d56c66..8b8c9e4a 100644 --- a/LANCommander.Launcher/UI/Components/PlayButton.razor +++ b/LANCommander.Launcher/UI/Components/PlayButton.razor @@ -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); } diff --git a/LANCommander.Launcher/UI/Components/RemoveFromLibraryDialog.razor b/LANCommander.Launcher/UI/Components/RemoveFromLibraryDialog.razor index 7a20e436..a60693a3 100644 --- a/LANCommander.Launcher/UI/Components/RemoveFromLibraryDialog.razor +++ b/LANCommander.Launcher/UI/Components/RemoveFromLibraryDialog.razor @@ -1,5 +1,6 @@ @using LANCommander.Launcher.Data.Models @using LANCommander.Launcher.Models +@using LANCommander.SDK.Enums @using LANCommander.SDK.Extensions @inherits FeedbackComponent @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() diff --git a/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameDetails.razor b/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameDetails.razor index 54a24463..a549678c 100644 --- a/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameDetails.razor +++ b/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameDetails.razor @@ -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(); diff --git a/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameItem.razor b/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameItem.razor index c3ccbce8..56c8ee13 100644 --- a/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameItem.razor +++ b/LANCommander.Launcher/UI/Pages/Depot/Components/DepotGameItem.razor @@ -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(); diff --git a/LANCommander.SDK/Models/GameInstallationArchive.cs b/LANCommander.SDK/Models/GameInstallationArchive.cs index 5dd6857e..32077842 100644 --- a/LANCommander.SDK/Models/GameInstallationArchive.cs +++ b/LANCommander.SDK/Models/GameInstallationArchive.cs @@ -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 Entries { get; private set; } = []; public List SavePaths { get; internal set; } } @@ -13,5 +14,5 @@ public class GameInstallationArchiveInfo public class GameInstallationArchiveEntries { public GameInstallationArchiveInfo BaseGame { get; internal set; } = new(); - public Dictionary DependentGames { get; private set; } = []; + public Dictionary Addons { get; private set; } = []; } diff --git a/LANCommander.SDK/Models/GameInstallationFileList.cs b/LANCommander.SDK/Models/GameInstallationFileList.cs index 6d0b4ea3..e12dd350 100644 --- a/LANCommander.SDK/Models/GameInstallationFileList.cs +++ b/LANCommander.SDK/Models/GameInstallationFileList.cs @@ -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 Files { get; private set; } = []; public void Merge(GameInstallationFileListEntry otherInfo) diff --git a/LANCommander.SDK/Models/GameManifest.cs b/LANCommander.SDK/Models/GameManifest.cs deleted file mode 100644 index 9ba97ea9..00000000 --- a/LANCommander.SDK/Models/GameManifest.cs +++ /dev/null @@ -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 Genre { get; set; } - public IEnumerable Tags { get; set; } - public IEnumerable Platforms { get; set; } - public IEnumerable Publishers { get; set; } - public IEnumerable Developers { get; set; } - public IEnumerable Collections { get; set; } - public string Version { get; set; } - public IEnumerable 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 SavePaths { get; set; } - public IEnumerable Keys { get; set; } - public IEnumerable