From 54400fdb10046bc28214183319d66b564ef3fc23 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 3 Dec 2025 02:22:37 -0600 Subject: [PATCH] Refactor server importers Refactors server importers to better handle dependencies on other data in the queue. --- .../Factories/ImportContextFactory.cs | 9 +- .../ImportContext.cs | 552 ++++++++++-------- .../Importers/ActionImporter.cs | 74 ++- .../Importers/ArchiveImporter.cs | 53 +- .../Importers/CollectionImporter.cs | 62 +- .../Importers/CustomFieldImporter.cs | 65 ++- .../Importers/DeveloperImporter.cs | 71 +-- .../Importers/EngineImporter.cs | 65 +-- .../Importers/GameImporter.cs | 132 ++++- .../Importers/GenreImporter.cs | 62 +- .../Importers/KeyImporter.cs | 55 +- .../Importers/MediaImporter.cs | 65 ++- .../Importers/MultiplayerModeImporter.cs | 72 ++- .../Importers/PlatformImporter.cs | 62 +- .../Importers/PlaySessionImporter.cs | 76 ++- .../Importers/PublisherImporter.cs | 66 +-- .../Importers/RedistributableImporter.cs | 45 +- .../Importers/SaveImporter.cs | 58 +- .../Importers/SavePathImporter.cs | 58 +- .../Importers/ScriptImporter.cs | 78 ++- .../Importers/ServerConsoleImporter.cs | 71 ++- .../Importers/ServerHttpPathImporter.cs | 68 ++- .../Importers/ServerImporter.cs | 34 +- .../Importers/TagImporter.cs | 64 +- .../Importers/_BaseImporter.cs | 29 +- .../Importers/_IImporter.cs | 11 +- .../Models/IImportItemInfo.cs | 12 + .../Models/ImportItemInfo.cs | 6 +- .../Models/ImportQueueItem.cs | 2 +- .../Models/ImportStatusUpdate.cs | 9 + .../Services/ImportService.cs | 31 +- .../_BaseDatabaseService.cs | 83 +++ 32 files changed, 1313 insertions(+), 887 deletions(-) create mode 100644 LANCommander.Server.ImportExport/Models/IImportItemInfo.cs create mode 100644 LANCommander.Server.ImportExport/Models/ImportStatusUpdate.cs diff --git a/LANCommander.Server.ImportExport/Factories/ImportContextFactory.cs b/LANCommander.Server.ImportExport/Factories/ImportContextFactory.cs index acf70a0f..c38df618 100644 --- a/LANCommander.Server.ImportExport/Factories/ImportContextFactory.cs +++ b/LANCommander.Server.ImportExport/Factories/ImportContextFactory.cs @@ -1,3 +1,4 @@ +using LANCommander.Server.ImportExport.Services; using Microsoft.Extensions.DependencyInjection; namespace LANCommander.Server.ImportExport.Factories; @@ -6,8 +7,12 @@ public class ImportContextFactory(IServiceProvider serviceProvider) { public ImportContext Create() { - var scope = serviceProvider.CreateScope(); + var importService = serviceProvider.GetRequiredService(); + + var context = new ImportContext(serviceProvider); + + importService.AddContext(context); - return scope.ServiceProvider.GetRequiredService(); + return context; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/ImportContext.cs b/LANCommander.Server.ImportExport/ImportContext.cs index 403fa0c2..5d9db8b1 100644 --- a/LANCommander.Server.ImportExport/ImportContext.cs +++ b/LANCommander.Server.ImportExport/ImportContext.cs @@ -1,120 +1,133 @@ -using System.IO.Compression; -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Helpers; using LANCommander.Server.Data.Models; using LANCommander.Server.ImportExport.Importers; using LANCommander.Server.ImportExport.Models; +using LANCommander.Server.ImportExport.Services; using LANCommander.Server.Services; -using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using ZipArchive = SharpCompress.Archives.Zip.ZipArchive; namespace LANCommander.Server.ImportExport; -public class ImportContext( - GameImporter gameImporter, - RedistributableImporter redistributableImporter, - ServerImporter serverImporter, - ActionImporter actionImporter, - ArchiveImporter archiveImporter, - CollectionImporter collectionImporter, - CustomFieldImporter customFieldImporter, - DeveloperImporter developerImporter, - PublisherImporter publisherImporter, - EngineImporter engineImporter, - GenreImporter genreImporter, - KeyImporter keyImporter, - MediaImporter mediaImporter, - MultiplayerModeImporter multiplayerModeImporter, - PlatformImporter platformImporter, - PlaySessionImporter playSessionImporter, - SaveImporter saveImporter, - SavePathImporter savePathImporter, - ScriptImporter scriptImporter, - ServerConsoleImporter serverConsoleImporter, - ServerHttpPathImporter serverHttpPathImporter, - TagImporter tagImporter, - StorageLocationService storageLocationService, - IMapper mapper, - ILogger logger) : IDisposable +public class ImportContext : IDisposable { + private Guid? Id { get; set; } public object Manifest { get; private set; } public BaseModel DataRecord { get; private set; } public StorageLocation ArchiveStorageLocation { get; set; } public ZipArchive Archive { get; private set; } - public GameImporter Games = gameImporter; - public RedistributableImporter Redistributables = redistributableImporter; - public ServerImporter Servers = serverImporter; + public IImportItemInfo CurrentItem { get; set; } + public int Processed => Queue.Count(qi => qi.Processed); + public int Total => Queue.Count; - public ActionImporter Actions = actionImporter; - public ArchiveImporter Archives = archiveImporter; - public CollectionImporter Collections = collectionImporter; - public CustomFieldImporter CustomFields = customFieldImporter; - public DeveloperImporter Developers = developerImporter; - public EngineImporter Engines = engineImporter; - public GenreImporter Genres = genreImporter; - public KeyImporter Keys = keyImporter; - public MediaImporter Media = mediaImporter; - public MultiplayerModeImporter MultiplayerModes = multiplayerModeImporter; - public PlatformImporter Platforms = platformImporter; - public PlaySessionImporter PlaySessions = playSessionImporter; - public PublisherImporter Publishers = publisherImporter; - public SaveImporter Saves = saveImporter; - public SavePathImporter SavePaths = savePathImporter; - public ScriptImporter Scripts = scriptImporter; - public ServerConsoleImporter ServerConsoles = serverConsoleImporter; - public ServerHttpPathImporter ServerHttpPaths = serverHttpPathImporter; - public TagImporter Tags = tagImporter; + private Queue Queue { get; } = new(); + private IEnumerable SelectedRecordIds { get; set; } = []; - public int Remaining => _queue.Count; - public int Processed => _queue.Count(qi => qi.Processed); - public int Total => _queue.Count; - - private List _queue { get; } = new(); - private IEnumerable _selectedRecordIds { get; set; } - public Dictionary Errored { get; } = new(); - - public EventHandler OnRecordAdded; - public EventHandler OnRecordProcessed; - public EventHandler OnRecordError; - - private void UseContext(ImportContext context) - { - Games.UseContext(context); - Redistributables.UseContext(context); - Servers.UseContext(context); - - Actions.UseContext(context); - Archives.UseContext(context); - Collections.UseContext(context); - CustomFields.UseContext(context); - Developers.UseContext(context); - Engines.UseContext(context); - Genres.UseContext(context); - Keys.UseContext(context); - Media.UseContext(context); - MultiplayerModes.UseContext(context); - Platforms.UseContext(context); - PlaySessions.UseContext(context); - Publishers.UseContext(context); - Saves.UseContext(context); - SavePaths.UseContext(context); - Scripts.UseContext(context); - ServerConsoles.UseContext(context); - ServerHttpPaths.UseContext(context); - Tags.UseContext(context); - } + public EventHandler OnImportStarted { get; set; } + public EventHandler OnImportStatusUpdate { get; set; } + public EventHandler OnImportComplete { get; set; } + public EventHandler OnImportError; - #region Initialize Import - public async Task> InitializeImportAsync(string archivePath) + private readonly ImportService _importService; + private readonly StorageLocationService _storageLocationService; + private readonly ILogger _logger; + + #region Importers + private readonly ActionImporter _actions; + private readonly ArchiveImporter _archives; + private readonly CollectionImporter _collections; + private readonly CustomFieldImporter _customFields; + private readonly DeveloperImporter _developers; + private readonly EngineImporter _engines; + private readonly GameImporter _games; + private readonly GenreImporter _genres; + private readonly KeyImporter _keys; + private readonly MediaImporter _media; + private readonly MultiplayerModeImporter _multiplayerModes; + private readonly PlatformImporter _platforms; + private readonly PlaySessionImporter _playSessions; + private readonly PublisherImporter _publishers; + private readonly RedistributableImporter _redistributables; + private readonly SaveImporter _saves; + private readonly SavePathImporter _savePaths; + private readonly ScriptImporter _scripts; + private readonly ServerConsoleImporter _serverConsoles; + private readonly ServerHttpPathImporter _serverHttpPaths; + private readonly ServerImporter _servers; + private readonly TagImporter _tags; + #endregion + + public ImportContext(IServiceProvider serviceProvider) { - UseContext(this); + _actions = serviceProvider.GetRequiredService(); + _archives = serviceProvider.GetRequiredService(); + _collections = serviceProvider.GetRequiredService(); + _customFields = serviceProvider.GetRequiredService(); + _developers = serviceProvider.GetRequiredService(); + _engines = serviceProvider.GetRequiredService(); + _games = serviceProvider.GetRequiredService(); + _genres = serviceProvider.GetRequiredService(); + _keys = serviceProvider.GetRequiredService(); + _media = serviceProvider.GetRequiredService(); + _multiplayerModes = serviceProvider.GetRequiredService(); + _platforms = serviceProvider.GetRequiredService(); + _playSessions = serviceProvider.GetRequiredService(); + _publishers = serviceProvider.GetRequiredService(); + _redistributables = serviceProvider.GetRequiredService(); + _saves = serviceProvider.GetRequiredService(); + _savePaths = serviceProvider.GetRequiredService(); + _scripts = serviceProvider.GetRequiredService(); + _serverConsoles = serviceProvider.GetRequiredService(); + _serverHttpPaths = serviceProvider.GetRequiredService(); + _servers = serviceProvider.GetRequiredService(); + _tags = serviceProvider.GetRequiredService(); + + _importService = serviceProvider.GetRequiredService(); + _storageLocationService = serviceProvider.GetRequiredService(); + _logger = serviceProvider.GetRequiredService>(); + } + + internal bool InQueue(TRecord record, BaseImporter importer) + where TRecord : class => + Queue.Any(qi => qi.Key == importer.GetKey(record)); + + public void SetId(Guid id) => Id = id; + + #region Initialize Import + public async Task> InitializeImportAsync(string archivePath) + { + _actions.UseContext(this); + _archives.UseContext(this); + _collections.UseContext(this); + _customFields.UseContext(this); + _developers.UseContext(this); + _engines.UseContext(this); + _games.UseContext(this); + _genres.UseContext(this); + _keys.UseContext(this); + _media.UseContext(this); + _multiplayerModes.UseContext(this); + _platforms.UseContext(this); + _playSessions.UseContext(this); + _publishers.UseContext(this); + _redistributables.UseContext(this); + _saves.UseContext(this); + _savePaths.UseContext(this); + _scripts.UseContext(this); + _serverConsoles.UseContext(this); + _serverHttpPaths.UseContext(this); + _servers.UseContext(this); + _tags.UseContext(this); Archive = ZipArchive.Open(archivePath); var manifestEntry = Archive.Entries.FirstOrDefault(e => e.Key == ManifestHelper.ManifestFilename); + + if (manifestEntry == null) + throw new InvalidOperationException("Invalid import file, cannot load manifest"); using (var reader = new StreamReader(manifestEntry.OpenEntryStream())) { @@ -133,214 +146,275 @@ public class ImportContext( } } - private async Task> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest) + private async Task> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest) { Manifest = gameManifest; - var importItemInfo = new List(); + var importItemInfo = new List(); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Actions, Actions).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Archives, Archives).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Collections, Collections).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.CustomFields, CustomFields).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Developers, Developers).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Actions, _actions).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Archives, _archives).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Collections, _collections).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.CustomFields, _customFields).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Developers, _developers).ToListAsync()); if (gameManifest.Engine != null) - importItemInfo.AddRange(await GetImportItemInfoAsync([gameManifest.Engine], Engines).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync([gameManifest.Engine], _engines).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Genres, Genres).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Keys, Keys).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Media, Media).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.MultiplayerModes, MultiplayerModes).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Platforms, Platforms).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.PlaySessions, PlaySessions).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Publishers, Publishers).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Saves, Saves).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.SavePaths, SavePaths).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Scripts, Scripts).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Tags, Tags).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Genres, _genres).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Keys, _keys).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Media, _media).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.MultiplayerModes, _multiplayerModes).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Platforms, _platforms).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.PlaySessions, _playSessions).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Publishers, _publishers).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Saves, _saves).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.SavePaths, _savePaths).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Scripts, _scripts).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(gameManifest.Tags, _tags).ToListAsync()); return importItemInfo; } - private async Task> InitializeRedistributableImportAsync(SDK.Models.Manifest.Redistributable redistributableManifest) + private async Task> InitializeRedistributableImportAsync(SDK.Models.Manifest.Redistributable redistributableManifest) { Manifest = redistributableManifest; - var importItemInfo = new List(); + var importItemInfo = new List(); - importItemInfo.AddRange(await GetImportItemInfoAsync(redistributableManifest.Archives, Archives).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(redistributableManifest.Scripts, Scripts).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(redistributableManifest.Archives, _archives).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(redistributableManifest.Scripts, _scripts).ToListAsync()); return importItemInfo; } - private async Task> InitializeServerImportAsync(SDK.Models.Manifest.Server serverManifest) + private async Task> InitializeServerImportAsync(SDK.Models.Manifest.Server serverManifest) { Manifest = serverManifest; - var importItemInfo = new List(); + var importItemInfo = new List(); - importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.Actions, Actions).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.Scripts, Scripts).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.ServerConsoles, ServerConsoles).ToListAsync()); - importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.HttpPaths, ServerHttpPaths).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.Actions, _actions).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.Scripts, _scripts).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.ServerConsoles, _serverConsoles).ToListAsync()); + importItemInfo.AddRange(await GetImportItemInfoAsync(serverManifest.HttpPaths, _serverHttpPaths).ToListAsync()); return importItemInfo; } #endregion - #region Prepare Import Queue public async Task PrepareImportQueueAsync(IEnumerable selectedRecordIds, Guid storageLocationId) { - _selectedRecordIds = selectedRecordIds; - - ArchiveStorageLocation = await storageLocationService.GetAsync(storageLocationId); + SelectedRecordIds = selectedRecordIds; + ArchiveStorageLocation = await _storageLocationService.GetAsync(storageLocationId); + if (Manifest is SDK.Models.Manifest.Game gameManifest) - await PrepareGameImportQueueAsync(gameManifest); + await AddAsync(gameManifest); if (Manifest is SDK.Models.Manifest.Redistributable redistributableManifest) - await PrepareRedistributableImportQueueAsync(redistributableManifest); + await AddAsync(redistributableManifest); if (Manifest is SDK.Models.Manifest.Server serverManifest) - await PrepareServerImportQueueAsync(serverManifest); + await AddAsync(serverManifest); } - public async Task PrepareGameImportQueueAsync(SDK.Models.Manifest.Game gameManifest) + public async Task AddAsync(SDK.Models.Manifest.Game game) { - if (!(await Games.ExistsAsync(gameManifest))) - DataRecord = await Games.AddAsync(gameManifest); - else - DataRecord = await Games.UpdateAsync(gameManifest); - await AddToImportQueueAsync(ImportExportRecordType.Action, gameManifest.Actions); - await AddToImportQueueAsync(ImportExportRecordType.Archive, gameManifest.Archives); - await AddToImportQueueAsync(ImportExportRecordType.Collection, gameManifest.Collections); - await AddToImportQueueAsync(ImportExportRecordType.CustomField, gameManifest.CustomFields); - await AddToImportQueueAsync(ImportExportRecordType.Developer, gameManifest.Developers); - await AddToImportQueueAsync(ImportExportRecordType.Engine, [gameManifest.Engine]); - await AddToImportQueueAsync(ImportExportRecordType.Genre, gameManifest.Genres); - await AddToImportQueueAsync(ImportExportRecordType.Key, gameManifest.Keys); - await AddToImportQueueAsync(ImportExportRecordType.Media, gameManifest.Media); - await AddToImportQueueAsync(ImportExportRecordType.MultiplayerMode, gameManifest.MultiplayerModes); - await AddToImportQueueAsync(ImportExportRecordType.Platform, gameManifest.Platforms); - await AddToImportQueueAsync(ImportExportRecordType.PlaySession, gameManifest.PlaySessions); - await AddToImportQueueAsync(ImportExportRecordType.Publisher, gameManifest.Publishers); - await AddToImportQueueAsync(ImportExportRecordType.Save, gameManifest.Saves); - await AddToImportQueueAsync(ImportExportRecordType.SavePath, gameManifest.SavePaths); - await AddToImportQueueAsync(ImportExportRecordType.Script, gameManifest.Scripts); - await AddToImportQueueAsync(ImportExportRecordType.Tag, gameManifest.Tags); + await AddAsync(game.Actions, _actions); + await AddAsync(game.Archives, _archives); + await AddAsync(game.Collections, _collections); + await AddAsync(game.CustomFields, _customFields); + await AddAsync(game.Developers, _developers); + await AddAsync(game.Engine, _engines); + await AddAsync(game.Genres, _genres); + await AddAsync(game.Keys, _keys); + await AddAsync(game.Media, _media); + await AddAsync(game.MultiplayerModes, _multiplayerModes); + await AddAsync(game.Platforms, _platforms); + await AddAsync(game.PlaySessions, _playSessions); + await AddAsync(game.Publishers, _publishers); + await AddAsync(game.Saves, _saves); + await AddAsync(game.Scripts, _scripts); + await AddAsync(game.Tags, _tags); + await AddAsync(game, _games); } - public async Task PrepareRedistributableImportQueueAsync(SDK.Models.Manifest.Redistributable redistributableManifest) - { - if (!(await Redistributables.ExistsAsync(redistributableManifest))) - DataRecord = await Redistributables.AddAsync(redistributableManifest); - else - DataRecord = await Redistributables.UpdateAsync(redistributableManifest); - - await AddToImportQueueAsync(ImportExportRecordType.Archive, redistributableManifest.Archives); - await AddToImportQueueAsync(ImportExportRecordType.Script, redistributableManifest.Scripts); - } - - public async Task PrepareServerImportQueueAsync(SDK.Models.Manifest.Server serverManifest) + public async Task AddAsync(SDK.Models.Manifest.Redistributable redistributable) { - if (!(await Servers.ExistsAsync(serverManifest))) - DataRecord = await Servers.AddAsync(serverManifest); - else - DataRecord = await Servers.UpdateAsync(serverManifest); - - await AddToImportQueueAsync(ImportExportRecordType.Action, serverManifest.Actions); - await AddToImportQueueAsync(ImportExportRecordType.Script, serverManifest.Scripts); - await AddToImportQueueAsync(ImportExportRecordType.ServerConsole, serverManifest.ServerConsoles); - await AddToImportQueueAsync(ImportExportRecordType.ServerHttpPath, serverManifest.HttpPaths); + await AddAsync(redistributable.Archives, _archives); + await AddAsync(redistributable.Scripts, _scripts); + await AddAsync(redistributable, _redistributables); } - #endregion - private async Task AddToImportQueueAsync(ImportExportRecordType type, IEnumerable records) where TRecord : SDK.Models.Manifest.BaseModel + public async Task AddAsync(SDK.Models.Manifest.Server server) { - if (records != null) - _queue.AddRange(records.Select(r => new ImportQueueItem(type, r))); + await AddAsync(server.Actions, _actions); + await AddAsync(server.Scripts, _scripts); + await AddAsync(server.HttpPaths, _serverHttpPaths); + await AddAsync(server.ServerConsoles, _serverConsoles); + await AddAsync(server, _servers); + } + + private async Task AddAsync(IEnumerable records, BaseImporter importer) + where TRecord : class + { + foreach (var record in records) + await AddAsync(record, importer); + } + + private async Task AddAsync(TRecord? record, BaseImporter importer) + where TRecord : class + { + if (record != null && !InQueue(record, importer) && await importer.CanImportAsync(record)) + Queue.Enqueue(await importer.GetImportInfoAsync(record)); } public async Task ImportQueueAsync() { - foreach (var queueItem in _queue) + OnImportStarted?.Invoke(this, new ImportStatusUpdate { - if (queueItem.Type == ImportExportRecordType.Action) - await ImportRecordAsync(queueItem, Actions); - else if (queueItem.Type == ImportExportRecordType.Archive) - await ImportRecordAsync(queueItem, Archives); - else if (queueItem.Type == ImportExportRecordType.Collection) - await ImportRecordAsync(queueItem, Collections); - else if (queueItem.Type == ImportExportRecordType.CustomField) - await ImportRecordAsync(queueItem, CustomFields); - else if (queueItem.Type == ImportExportRecordType.Developer) - await ImportRecordAsync(queueItem, Developers); - else if (queueItem.Type == ImportExportRecordType.Publisher) - await ImportRecordAsync(queueItem, Publishers); - else if (queueItem.Type == ImportExportRecordType.Engine) - await ImportRecordAsync(queueItem, Engines); - else if (queueItem.Type == ImportExportRecordType.Genre) - await ImportRecordAsync(queueItem, Genres); - else if (queueItem.Type == ImportExportRecordType.Key) - await ImportRecordAsync(queueItem, Keys); - else if (queueItem.Type == ImportExportRecordType.Media) - await ImportRecordAsync(queueItem, Media); - else if (queueItem.Type == ImportExportRecordType.MultiplayerMode) - await ImportRecordAsync(queueItem, MultiplayerModes); - else if (queueItem.Type == ImportExportRecordType.Platform) - await ImportRecordAsync(queueItem, Platforms); - else if (queueItem.Type == ImportExportRecordType.PlaySession) - await ImportRecordAsync(queueItem, PlaySessions); - else if (queueItem.Type == ImportExportRecordType.Save) - await ImportRecordAsync(queueItem, Saves); - else if (queueItem.Type == ImportExportRecordType.SavePath) - await ImportRecordAsync(queueItem, SavePaths); - else if (queueItem.Type == ImportExportRecordType.Script) - await ImportRecordAsync(queueItem, Scripts); - else if (queueItem.Type == ImportExportRecordType.ServerConsole) - await ImportRecordAsync(queueItem, ServerConsoles); - else if (queueItem.Type == ImportExportRecordType.ServerHttpPath) - await ImportRecordAsync(queueItem, ServerHttpPaths); - else if (queueItem.Type == ImportExportRecordType.Tag) - await ImportRecordAsync(queueItem, Tags); + Index = -1, + Total = Queue.Count, + }); + + int deferred = 0; + + while (Queue.Count > 0) + { + var queueItem = Queue.Dequeue(); + + OnImportStatusUpdate?.Invoke(this, new ImportStatusUpdate + { + CurrentItem = queueItem, + Index = Processed, + Total = Total, + }); + + var success = await TryImportAsync(queueItem); + + if (success) + { + deferred = 0; + continue; + } + + Queue.Enqueue(queueItem); + deferred++; + + if (deferred >= Queue.Count) + throw new InvalidOperationException("Import deadlocked: remaining jobs cannot be satisfied."); } + + OnImportComplete?.Invoke(this, new ImportStatusUpdate + { + Index = Total - 1, + Total = Total, + }); + + _importService.RemoveContext(Id.Value); + } + + private async Task TryImportAsync(IImportItemInfo queueItem) + { + try + { + switch (queueItem.Type) + { + case ImportExportRecordType.Action: + return await _actions.ImportAsync(queueItem); + + case ImportExportRecordType.Archive: + return await _archives.ImportAsync(queueItem); + + case ImportExportRecordType.Collection: + return await _collections.ImportAsync(queueItem); + + case ImportExportRecordType.CustomField: + return await _customFields.ImportAsync(queueItem); + + case ImportExportRecordType.Developer: + return await _developers.ImportAsync(queueItem); + + case ImportExportRecordType.Engine: + return await _engines.ImportAsync(queueItem); + + case ImportExportRecordType.Game: + return await _games.ImportAsync(queueItem); + + case ImportExportRecordType.Genre: + return await _genres.ImportAsync(queueItem); + + case ImportExportRecordType.Key: + return await _keys.ImportAsync(queueItem); + + case ImportExportRecordType.Media: + return await _media.ImportAsync(queueItem); + + case ImportExportRecordType.MultiplayerMode: + return await _multiplayerModes.ImportAsync(queueItem); + + case ImportExportRecordType.Platform: + return await _platforms.ImportAsync(queueItem); + + case ImportExportRecordType.PlaySession: + return await _playSessions.ImportAsync(queueItem); + + case ImportExportRecordType.Publisher: + return await _publishers.ImportAsync(queueItem); + + case ImportExportRecordType.Redistributable: + return await _redistributables.ImportAsync(queueItem); + + case ImportExportRecordType.Save: + return await _saves.ImportAsync(queueItem); + + case ImportExportRecordType.SavePath: + return await _savePaths.ImportAsync(queueItem); + + case ImportExportRecordType.Script: + return await _scripts.ImportAsync(queueItem); + + case ImportExportRecordType.Server: + return await _servers.ImportAsync(queueItem); + + case ImportExportRecordType.ServerConsole: + return await _serverConsoles.ImportAsync(queueItem); + + case ImportExportRecordType.ServerHttpPath: + return await _serverHttpPaths.ImportAsync(queueItem); + + case ImportExportRecordType.Tag: + return await _tags.ImportAsync(queueItem); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error importing record {RecordName}", queueItem.Name); + + OnImportError?.Invoke(this, new ImportStatusUpdate + { + CurrentItem = CurrentItem, + Index = Processed, + Total = Total, + Error = ex.Message, + }); + } + + return false; } - private async IAsyncEnumerable GetImportItemInfoAsync(IEnumerable records, - BaseImporter importer) + private async IAsyncEnumerable> GetImportItemInfoAsync(IEnumerable records, + BaseImporter importer) where TRecord : class { if (records != null) foreach (var record in records) { - if (record != null && record.GetType() == typeof(TModel)) + if (record != null && record.GetType() == typeof(TRecord)) yield return await importer.GetImportInfoAsync(record); } } - private async Task ImportRecordAsync(ImportQueueItem queueItem, BaseImporter importer) where TRecord : class - { - var record = queueItem.Record as TRecord; - - try - { - if (await importer.ExistsAsync(record)) - await importer.UpdateAsync(record); - else - await importer.AddAsync(record); - - queueItem.Processed = true; - - OnRecordProcessed?.Invoke(this, queueItem); - } - catch (Exception ex) - { - Errored.Add(queueItem, ex.Message); - OnRecordError?.Invoke(this, queueItem); - } - } - public void Dispose() { if (Archive != null) diff --git a/LANCommander.Server.ImportExport/Importers/ActionImporter.cs b/LANCommander.Server.ImportExport/Importers/ActionImporter.cs index 436f2557..885f6866 100644 --- a/LANCommander.Server.ImportExport/Importers/ActionImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/ActionImporter.cs @@ -1,49 +1,78 @@ -using AutoMapper; using LANCommander.SDK.Enums; -using LANCommander.Server.ImportExport.Exceptions; +using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; using Action = LANCommander.SDK.Models.Manifest.Action; namespace LANCommander.Server.ImportExport.Importers; public class ActionImporter( - IMapper mapper, - ActionService actionService) : BaseImporter + ILogger logger, + ActionService actionService, + GameService gameService, + ServerService serverService, + GameImporter gameImporter, + ServerImporter serverImporter) : BaseImporter { - public override async Task GetImportInfoAsync(Action record) => - await Task.Run(() => new ImportItemInfo { Name = record.Name, Type = ImportExportRecordType.Action }); + public override string GetKey(Action record) + => $"{nameof(Action)}/{record.Name}"; - public override bool CanImport(Action record) => ImportContext.DataRecord is Data.Models.Game; - + public override async Task> GetImportInfoAsync(Action record) + => new() + { + Name = record.Name, + Type = ImportExportRecordType.Action, + Record = record, + }; - public override async Task AddAsync(Action record) + public override async Task CanImportAsync(Action record) => ImportContext.Manifest is Game; + + public override async Task AddAsync(Action record) { try { var action = new Data.Models.Action { Name = record.Name, - Game = ImportContext.DataRecord as Data.Models.Game, Path = record.Path, WorkingDirectory = record.WorkingDirectory, PrimaryAction = record.IsPrimaryAction, SortOrder = record.SortOrder, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - action = await actionService.AddAsync(action); + if (ImportContext.Manifest is Game game && !ImportContext.InQueue(game, gameImporter)) + action.Game = await gameService.GetAsync(game.Id); + else if (ImportContext.Manifest is SDK.Models.Manifest.Server server && + !ImportContext.InQueue(server, serverImporter)) + action.Server = await serverService.GetAsync(server.Id); + else + return false; - return action; + await actionService.AddAsync(action); + + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing action", ex); + logger.LogError(ex, "Could not add action | {Key}", GetKey(record)); + + return false; } } - public override async Task UpdateAsync(Action record) + public override async Task UpdateAsync(Action record) { - var existing = await actionService.FirstOrDefaultAsync(a => a.Name == record.Name); + Data.Models.Action existing; + + if (ImportContext.Manifest is Game game) + existing = await actionService.FirstOrDefaultAsync(a => a.Name == record.Name && a.GameId == game.Id); + else if (ImportContext.Manifest is SDK.Models.Manifest.Server server) + existing = await actionService.FirstOrDefaultAsync(a => a.Name == record.Name && a.ServerId == server.Id); + else + return false; try { @@ -51,20 +80,25 @@ public class ActionImporter( existing.WorkingDirectory = record.WorkingDirectory; existing.PrimaryAction = record.IsPrimaryAction; existing.SortOrder = record.SortOrder; - existing.Game = ImportContext.DataRecord as Data.Models.Game; + existing.CreatedOn = record.CreatedOn; + existing.UpdatedOn = record.UpdatedOn; - existing = await actionService.UpdateAsync(existing); + await actionService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing action", ex); + logger.LogError(ex, "Could not update action | {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(Action record) { - return await actionService.ExistsAsync(a => a.Name == record.Name && a.GameId == ImportContext.DataRecord.Id); + if (ImportContext.Manifest is Game game) + return await actionService.ExistsAsync(a => a.Name == record.Name && a.GameId == game.Id); + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/ArchiveImporter.cs b/LANCommander.Server.ImportExport/Importers/ArchiveImporter.cs index 483d8e39..02212871 100644 --- a/LANCommander.Server.ImportExport/Importers/ArchiveImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/ArchiveImporter.cs @@ -4,6 +4,7 @@ using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; @@ -13,22 +14,26 @@ namespace LANCommander.Server.ImportExport.Importers; /// Valid service provider for injecting the services we need /// The context (archive, parent record> of the import public class ArchiveImporter( - IMapper mapper, - ArchiveService archiveService) : BaseImporter + ILogger logger, + ArchiveService archiveService, + GameService gameService, + RedistributableService redistributableService) : BaseImporter { - public override async Task GetImportInfoAsync(Archive record) - { - return new ImportItemInfo + public override string GetKey(Archive record) + => $"{nameof(Archive)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(Archive record) + => new() { Type = ImportExportRecordType.Archive, Name = record.Version, Size = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Archives/{record.Id}")?.Size ?? 0, + Record = record, }; - } - public override bool CanImport(Archive record) => ImportContext.DataRecord is Data.Models.Game || ImportContext.DataRecord is Data.Models.Redistributable; + public override async Task CanImportAsync(Archive record) => ImportContext.Manifest is Game || ImportContext.Manifest is Redistributable; - public override async Task AddAsync(Archive record) + public override async Task AddAsync(Archive record) { var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Archives/{record.Id}"); @@ -43,6 +48,7 @@ public class ArchiveImporter( var newArchive = new Data.Models.Archive() { CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, StorageLocation = ImportContext.ArchiveStorageLocation, Version = record.Version, Changelog = record.Changelog, @@ -51,10 +57,10 @@ public class ArchiveImporter( UncompressedSize = record.UncompressedSize, }; - if (ImportContext.DataRecord is Data.Models.Game game) - newArchive.Game = game; - else if (ImportContext.DataRecord is Data.Models.Redistributable redistributable) - newArchive.Redistributable = redistributable; + if (ImportContext.Manifest is Game game) + newArchive.Game = await gameService.GetAsync(game.Id); + else if (ImportContext.Manifest is Redistributable redistributable) + newArchive.Redistributable = await redistributableService.GetAsync(redistributable.Id); else throw new ImportSkippedException(record, $"Cannot import an archive for a {record.GetType().Name}"); @@ -62,18 +68,19 @@ public class ArchiveImporter( archive = await archiveService.AddAsync(newArchive); archive = await archiveService.WriteToFileAsync(archive, archiveEntry.OpenEntryStream()); - return archive; + return true; } catch (Exception ex) { if (archive != null) await archiveService.DeleteAsync(archive); - throw new ImportSkippedException(record, "An unknown error occured while importing archive file", ex); + logger.LogError(ex, "Could not add archive | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Archive archive) + public override async Task UpdateAsync(Archive archive) { var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Archives/{archive.Id}"); var existing = await archiveService.Include(a => a.StorageLocation).FirstOrDefaultAsync(a => archive.Id == a.Id); @@ -87,29 +94,33 @@ public class ArchiveImporter( existing.Version = archive.Version; existing.Changelog = archive.Changelog; existing.StorageLocation = ImportContext.ArchiveStorageLocation; + existing.CreatedOn = archive.CreatedOn; + existing.UpdatedOn = archive.UpdatedOn; existing = await archiveService.UpdateAsync(existing); - existing = await archiveService.WriteToFileAsync(existing, archiveEntry.OpenEntryStream()); + + await archiveService.WriteToFileAsync(existing, archiveEntry.OpenEntryStream()); if (File.Exists(existingPath)) File.Delete(existingPath); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(archive, "An unknown error occured while importing archive file", ex); + logger.LogError(ex, "Could not update archive | {Key}", GetKey(archive)); + return false; } } public override async Task ExistsAsync(Archive archive) { - if (ImportContext.DataRecord is Data.Models.Game game) + if (ImportContext.Manifest is Game game) return await archiveService.ExistsAsync(a => a.Version == archive.Version && a.GameId == game.Id); - if (ImportContext.DataRecord is Data.Models.Redistributable redistributable) + if (ImportContext.Manifest is Redistributable redistributable) return await archiveService.ExistsAsync(a => a.Version == archive.Version && a.RedistributableId == redistributable.Id); - throw new ImportSkippedException(archive, $"Cannot import an archive for a {ImportContext.DataRecord.GetType().Name}"); + throw new ImportSkippedException(archive, $"Cannot import archive, incompatible manifest"); } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/CollectionImporter.cs b/LANCommander.Server.ImportExport/Importers/CollectionImporter.cs index b1eaf26d..8fcd7c88 100644 --- a/LANCommander.Server.ImportExport/Importers/CollectionImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/CollectionImporter.cs @@ -1,75 +1,53 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class CollectionImporter( - IMapper mapper, - CollectionService collectionService, - GameService gameService) : BaseImporter + ILogger logger, + CollectionService collectionService) : BaseImporter { - public override async Task GetImportInfoAsync(Collection record) - { - return new ImportItemInfo + public override string GetKey(Collection record) + => $"{nameof(Collection)}/{record.Name}"; + + public override async Task> GetImportInfoAsync(Collection record) + => new() { Type = ImportExportRecordType.Collection, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Collection record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Collection record) + => await collectionService.ExistsAsync(c => c.Name == record.Name); - public override async Task AddAsync(Collection record) + public override async Task AddAsync(Collection record) { try { var collection = new Data.Models.Collection { - Games = new List() { ImportContext.DataRecord as Data.Models.Game }, Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - collection = await collectionService.AddAsync(collection); + await collectionService.AddAsync(collection); - return collection; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing collection", ex); + logger.LogError(ex, "Could not add collection | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Collection record) - { - var existing = await collectionService.Include(c => c.Games).FirstOrDefaultAsync(c => c.Name == record.Name); - var game = ImportContext.DataRecord as Data.Models.Game; - - try - { - if (existing.Games == null) - existing.Games = new List(); - - if (!existing.Games.Any(g => g.Id == game.Id)) - { - existing.Games.Add(await gameService.GetAsync(game.Id)); - - existing = await collectionService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing collection", ex); - } - } + public override async Task UpdateAsync(Collection record) => true; public override async Task ExistsAsync(Collection record) - { - return await collectionService.ExistsAsync(c => c.Name == record.Name); - } + => await collectionService.ExistsAsync(c => c.Name == record.Name); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/CustomFieldImporter.cs b/LANCommander.Server.ImportExport/Importers/CustomFieldImporter.cs index 8e40a6ed..8e792cbe 100644 --- a/LANCommander.Server.ImportExport/Importers/CustomFieldImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/CustomFieldImporter.cs @@ -4,58 +4,83 @@ using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class CustomFieldImporter( - IMapper mapper, - GameCustomFieldService gameCustomFieldService, - GameService gameService) : BaseImporter + ILogger logger, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(GameCustomField record) - { - return new ImportItemInfo + public override string GetKey(GameCustomField record) + => $"{nameof(GameCustomField)}/{record.Name}"; + + public override async Task> GetImportInfoAsync(GameCustomField record) + => new() { Type = ImportExportRecordType.CustomField, Name = record.Name, + Record = record, }; - } - - public override bool CanImport(GameCustomField record) => ImportContext.DataRecord is Data.Models.Game; - public override async Task AddAsync(GameCustomField record) + public override async Task CanImportAsync(GameCustomField record) => ImportContext.Manifest is Game; + + public override async Task AddAsync(GameCustomField record) { try { - var customField = await gameService.SetCustomFieldAsync(ImportContext.DataRecord.Id, record.Name, record.Value); + var game = ImportContext.Manifest as Game; - return customField; + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + + await gameService.SetCustomFieldAsync(game.Id, record.Name, record.Value); + + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing customField", ex); + logger.LogError(ex, "Could not add custom field | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(GameCustomField record) + public override async Task UpdateAsync(GameCustomField record) { - var existing = await gameService.GetCustomFieldAsync(ImportContext.DataRecord.Id, record.Name); - try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + var existing = await gameService.GetCustomFieldAsync(game.Id, record.Name); + if (existing.Value != record.Value) - existing = await gameService.SetCustomFieldAsync(ImportContext.DataRecord.Id, record.Name, record.Value); + await gameService.SetCustomFieldAsync(game.Id, record.Name, record.Value); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing customField", ex); + logger.LogError(ex, "Could not update custom field | {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(GameCustomField record) { - return (await gameService.GetCustomFieldAsync(ImportContext.DataRecord.Id, record.Name)) == null; + if (ImportContext.Manifest is Game game) + { + var customField = await gameService.GetCustomFieldAsync(game.Id, record.Name); + + return customField != null; + } + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/DeveloperImporter.cs b/LANCommander.Server.ImportExport/Importers/DeveloperImporter.cs index 1fac8f92..bcdecf7f 100644 --- a/LANCommander.Server.ImportExport/Importers/DeveloperImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/DeveloperImporter.cs @@ -4,84 +4,53 @@ using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class DeveloperImporter( - IMapper mapper, + ILogger logger, CompanyService companyService, - GameService gameService) : BaseImporter + GameService gameService) : BaseImporter { - public override async Task GetImportInfoAsync(Company record) - { - return new ImportItemInfo + public override string GetKey(Company record) + => $"Developer/{record.Name}"; + + public override async Task> GetImportInfoAsync(Company record) + => new() { Type = ImportExportRecordType.Developer, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Company record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Company record) + => await companyService.ExistsAsync(c => c.Name == record.Name); - public override async Task AddAsync(Company record) + public override async Task AddAsync(Company record) { - if (ImportContext.DataRecord is not Data.Models.Game) - throw new ImportSkippedException(record, $"Cannot import developers for a {ImportContext.DataRecord.GetType().Name}"); - try { - var game = ImportContext.DataRecord as Data.Models.Game; - var company = new Data.Models.Company { - DevelopedGames = [await gameService.GetAsync(game.Id)], Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - company = await companyService.AddAsync(company); + await companyService.AddAsync(company); - return company; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing developer", ex); + logger.LogError(ex, "Could not import developer | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Company record) - { - if (ImportContext.DataRecord is not Data.Models.Game) - throw new ImportSkippedException(record, $"Cannot import developers for a {ImportContext.DataRecord.GetType().Name}"); - - var existing = await companyService.Include(g => g.DevelopedGames).FirstOrDefaultAsync(c => c.Name == record.Name); - - try - { - var game = ImportContext.DataRecord as Data.Models.Game; - - if (existing.DevelopedGames == null) - existing.DevelopedGames = new List(); - - if (!existing.DevelopedGames.Any(g => g.Id == game.Id)) - { - existing.DevelopedGames.Add(await gameService.GetAsync(game.Id)); - - existing = await companyService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing developer", ex); - } - } + public override async Task UpdateAsync(Company record) => true; public override async Task ExistsAsync(Company record) - { - if (ImportContext.DataRecord is not Data.Models.Game game) - throw new ImportSkippedException(record, $"Cannot import developers for a {ImportContext.DataRecord.GetType().Name}"); - - return await companyService.ExistsAsync(c => c.Name == record.Name); - } + => await companyService.ExistsAsync(c => c.Name == record.Name); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/EngineImporter.cs b/LANCommander.Server.ImportExport/Importers/EngineImporter.cs index 367f24d5..f2e8cfca 100644 --- a/LANCommander.Server.ImportExport/Importers/EngineImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/EngineImporter.cs @@ -1,76 +1,53 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class EngineImporter( - IMapper mapper, - EngineService engineService, - GameService gameService) : BaseImporter + ILogger logger, + EngineService engineService) : BaseImporter { - public override async Task GetImportInfoAsync(Engine record) - { - return new ImportItemInfo + public override string GetKey(Engine record) + => $"{nameof(Engine)}/{record.Name}"; + + public override async Task> GetImportInfoAsync(Engine record) + => new() { Type = ImportExportRecordType.Engine, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Engine record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Engine record) + => await engineService.ExistsAsync(e => e.Name == record.Name); - public override async Task AddAsync(Engine record) + public override async Task AddAsync(Engine record) { try { var engine = new Data.Models.Engine { - Games = new List() { ImportContext.DataRecord as Data.Models.Game }, Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - engine = await engineService.AddAsync(engine); + await engineService.AddAsync(engine); - return engine; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing engine", ex); + logger.LogError(ex, "Could not add engine | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Engine record) - { - var existing = await engineService.Include(g => g.Games).FirstOrDefaultAsync(c => c.Name == record.Name); - var game = ImportContext.DataRecord as Data.Models.Game; - - try - { - - if (existing.Games == null) - existing.Games = new List(); + public override async Task UpdateAsync(Engine record) => true; - if (!existing.Games.Any(g => g.Id == game.Id)) - { - existing.Games.Add(await gameService.GetAsync(game.Id)); - - existing = await engineService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing engine", ex); - } - } - - public override async Task ExistsAsync(Engine record) - { - return await engineService.ExistsAsync(c => c.Name == record.Name); - } + 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.Server.ImportExport/Importers/GameImporter.cs b/LANCommander.Server.ImportExport/Importers/GameImporter.cs index 741c7248..a9af0904 100644 --- a/LANCommander.Server.ImportExport/Importers/GameImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/GameImporter.cs @@ -1,30 +1,36 @@ -using AutoMapper; +using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class GameImporter( - IMapper mapper, + ILogger logger, GameService gameService, - UserService userService) : BaseImporter + UserService userService) : BaseImporter { - public override async Task GetImportInfoAsync(Game record) + public override string GetKey(Game record) + => $"{nameof(Game)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(Game record) { - return new ImportItemInfo + return new ImportItemInfo { + Type = ImportExportRecordType.Game, Name = record.Title, + Record = record, }; } - public override bool CanImport(Game record) => true; + public override async Task CanImportAsync(Game record) => true; - public override async Task AddAsync(Game record) + public override async Task AddAsync(Game record) { var game = new Data.Models.Game { + Id = record.Id, Title = record.Title, SortTitle = record.SortTitle, Description = record.Description, @@ -46,15 +52,18 @@ public class GameImporter( try { - return await gameService.AddAsync(game); + await gameService.AddAsync(game); + + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occurred while trying to add game", ex); + logger.LogError(ex, "Could not add game | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Game record) + public override async Task UpdateAsync(Game record) { var existing = await gameService.FirstOrDefaultAsync(g => g.Id == record.Id || g.Title == record.Title); @@ -77,15 +86,14 @@ public class GameImporter( if (!String.IsNullOrWhiteSpace(record.UpdatedBy)) existing.UpdatedBy = await userService.GetAsync(record.UpdatedBy); - existing = await gameService.UpdateAsync(existing); - - // importContext.UseRecord(existing); + await gameService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occurred while trying to update game", ex); + logger.LogError(ex, "Could not update game | {Key}", GetKey(record)); + return false; } } @@ -93,4 +101,96 @@ public class GameImporter( { return await gameService.ExistsAsync(g => g.Id == record.Id || g.Title == record.Title); } + + public async Task FinalizeAsync() + { + if (ImportContext.Manifest is not Game) + return; + + var manifest = ImportContext.Manifest as Game; + + if (manifest == null) + return; + + var game = await gameService + .Include(g => g.Collections) + .Include(g => g.Developers) + .Include(g => g.Genres) + .Include(g => g.Platforms) + .Include(g => g.Publishers) + .Include(g => g.Tags) + .GetAsync(manifest.Id); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Collections, + manifest.Collections, + r => c => c.Name == r.Name, + (c, rc) => + { + c.Name = rc.Name; + c.CreatedOn = rc.CreatedOn; + c.UpdatedOn = rc.UpdatedOn; + }); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Developers, + manifest.Developers, + r => c => c.Name == r.Name, + (d, rd) => + { + d.Name = rd.Name; + d.CreatedOn = rd.CreatedOn; + d.UpdatedOn = rd.UpdatedOn; + }); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Genres, + manifest.Genres, + r => g => g.Name == r.Name, + (g, gr) => + { + g.Name = gr.Name; + g.CreatedOn = gr.CreatedOn; + g.UpdatedOn = gr.UpdatedOn; + }); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Platforms, + manifest.Platforms, + r => p => p.Name == r.Name, + (p, pr) => + { + p.Name = pr.Name; + p.CreatedOn = pr.CreatedOn; + p.UpdatedOn = pr.UpdatedOn; + }); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Publishers, + manifest.Publishers, + r => p => p.Name == r.Name, + (p, pr) => + { + p.Name = pr.Name; + p.CreatedOn = pr.CreatedOn; + p.UpdatedOn = pr.UpdatedOn; + }); + + await gameService.SyncRelatedCollectionAsync( + game, + g => g.Tags, + manifest.Tags, + r => t => t.Name == r.Name, + (t, tr) => + { + t.Name = tr.Name; + t.CreatedOn = tr.CreatedOn; + t.UpdatedOn = tr.UpdatedOn; + }); + } } diff --git a/LANCommander.Server.ImportExport/Importers/GenreImporter.cs b/LANCommander.Server.ImportExport/Importers/GenreImporter.cs index 7802f9ed..e8159986 100644 --- a/LANCommander.Server.ImportExport/Importers/GenreImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/GenreImporter.cs @@ -1,75 +1,55 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class GenreImporter( - IMapper mapper, - GenreService genreService, - GameService gameService) : BaseImporter + ILogger logger, + GenreService genreService) : BaseImporter { - public override async Task GetImportInfoAsync(Genre record) + public override string GetKey(Genre record) + => $"{nameof(Genre)}/{record.Name}"; + + public override async Task> GetImportInfoAsync(Genre record) { - return new ImportItemInfo + return new ImportItemInfo { Type = ImportExportRecordType.Genre, Name = record.Name, + Record = record, }; } - public override bool CanImport(Genre record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Genre record) + => await genreService.ExistsAsync(g => g.Name == record.Name); - public override async Task AddAsync(Genre record) + public override async Task AddAsync(Genre record) { try { var genre = new Data.Models.Genre { - Games = new List() { ImportContext.DataRecord as Data.Models.Game }, Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - genre = await genreService.AddAsync(genre); + await genreService.AddAsync(genre); - return genre; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing genre", ex); + logger.LogError(ex, "Could not add genre | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Genre record) - { - var existing = await genreService.Include(g => g.Games).FirstOrDefaultAsync(c => c.Name == record.Name); - var game = ImportContext.DataRecord as Data.Models.Game; - - try - { - if (existing.Games == null) - existing.Games = new List(); + public override async Task UpdateAsync(Genre record) => true; - if (!existing.Games.Any(g => g.Id == game.Id)) - { - existing.Games.Add(await gameService.GetAsync(game.Id)); - - existing = await genreService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing genre", ex); - } - } - - public override async Task ExistsAsync(Genre record) - { - return await genreService.ExistsAsync(c => c.Name == record.Name); - } + 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.Server.ImportExport/Importers/KeyImporter.cs b/LANCommander.Server.ImportExport/Importers/KeyImporter.cs index d742382d..63f18ef9 100644 --- a/LANCommander.Server.ImportExport/Importers/KeyImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/KeyImporter.cs @@ -1,51 +1,68 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class KeyImporter( - IMapper mapper, - KeyService keyService) : BaseImporter + ILogger logger, + KeyService keyService, + GameImporter gameImporter, + GameService gameService) : BaseImporter { - public override async Task GetImportInfoAsync(Key record) + public override string GetKey(Key record) + => $"{nameof(Key)}/{record.Value}"; + + public override async Task> GetImportInfoAsync(Key record) { - return new ImportItemInfo + return new ImportItemInfo { Type = ImportExportRecordType.Key, Name = new String('*', record.Value.Length), + Record = record, }; } - public override bool CanImport(Key record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Key record) + => await keyService.ExistsAsync(k => k.Value == record.Value); - public override async Task AddAsync(Key record) + public override async Task AddAsync(Key record) { try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + var key = new Data.Models.Key { - Game = ImportContext.DataRecord as Data.Models.Game, + Game = await gameService.GetAsync(game.Id), AllocationMethod = record.AllocationMethod, ClaimedByComputerName = record.ClaimedByComputerName, ClaimedByIpv4Address = record.ClaimedByIpv4Address, ClaimedByMacAddress = record.ClaimedByMacAddress, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - key = await keyService.AddAsync(key); + await keyService.AddAsync(key); - return key; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing key", ex); + logger.LogError(ex, "Could not add key | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Key record) + public override async Task UpdateAsync(Key record) { var existing = await keyService.FirstOrDefaultAsync(k => k.Value == record.Value); @@ -56,18 +73,22 @@ public class KeyImporter( existing.ClaimedByIpv4Address = record.ClaimedByIpv4Address; existing.ClaimedByMacAddress = record.ClaimedByMacAddress; - existing = await keyService.UpdateAsync(existing); + await keyService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing key", ex); + logger.LogError(ex, "Could not update key | {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(Key record) { - return await keyService.ExistsAsync(k => k.Value == record.Value); + if (ImportContext.Manifest is Game game) + return await keyService.ExistsAsync(k => k.Value == record.Value && k.GameId == game.Id); + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/MediaImporter.cs b/LANCommander.Server.ImportExport/Importers/MediaImporter.cs index a4d8bc1b..589361e5 100644 --- a/LANCommander.Server.ImportExport/Importers/MediaImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/MediaImporter.cs @@ -1,4 +1,3 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; @@ -9,24 +8,29 @@ using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class MediaImporter( - IMapper mapper, ILogger logger, StorageLocationService storageLocationService, - MediaService mediaService) : BaseImporter + MediaService mediaService, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(Media record) + public override string GetKey(Media record) + => $"{nameof(Media)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(Media record) { - return new ImportItemInfo + return new ImportItemInfo { Type = ImportExportRecordType.Media, Name = String.IsNullOrWhiteSpace(record.Name) ? record.Type.ToString() : $"{record.Type} - {record.Name}", Size = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Media/{record.Id}")?.Size ?? 0, + Record = record, }; } - public override bool CanImport(Media record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Media record) => ImportContext.Manifest is Game; - public override async Task AddAsync(Media record) + public override async Task AddAsync(Media record) { var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Media/{record.Id}"); @@ -37,9 +41,19 @@ public class MediaImporter( try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + media = new Data.Models.Media { - Game = ImportContext.DataRecord as Data.Models.Game, + Id = record.Id, + FileId = record.FileId, + Game = await gameService.GetAsync(record.Id), CreatedOn = record.CreatedOn, Type = record.Type, UpdatedOn = record.UpdatedOn, @@ -50,23 +64,23 @@ public class MediaImporter( }; media = await mediaService.AddAsync(media); - media = await mediaService.WriteToFileAsync(media, archiveEntry.OpenEntryStream()); + + await mediaService.WriteToFileAsync(media, archiveEntry.OpenEntryStream()); - return media; + return true; } catch (Exception ex) { if (media?.Id != Guid.Empty) await mediaService.DeleteAsync(media); - logger.LogError(ex, "An unknown error occured while trying to import media file"); + logger.LogError(ex, "An unknown error occured while trying to import media file | {Key}", GetKey(record)); - throw new ImportSkippedException(record, "An unknown error occured while trying to import media file", - ex); + return false; } } - public override async Task UpdateAsync(Media record) + public override async Task UpdateAsync(Media record) { var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Media/{record.Id}"); var existing = await mediaService.Include(m => m.StorageLocation).FirstOrDefaultAsync(m => m.Type == record.Type && m.Game.Id == record.Id); @@ -77,7 +91,16 @@ public class MediaImporter( try { - existing.Game = ImportContext.DataRecord as Data.Models.Game; + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + + existing.FileId = record.FileId; + existing.Game = await gameService.GetAsync(game.Id); existing.Name = record.Name; existing.MimeType = record.MimeType; existing.CreatedOn = record.CreatedOn; @@ -86,21 +109,21 @@ public class MediaImporter( existing.SourceUrl = record.SourceUrl; existing = await mediaService.UpdateAsync(existing); - existing = await mediaService.WriteToFileAsync(existing, archiveEntry.OpenEntryStream()); + + await mediaService.WriteToFileAsync(existing, archiveEntry.OpenEntryStream()); if (File.Exists(existingPath)) File.Delete(existingPath); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing media file", ex); + logger.LogError(ex, "Could not update media | {Key}", GetKey(record)); + return false; } } public override Task ExistsAsync(Media media) - { - return mediaService.ExistsAsync(m => m.Type == media.Type && m.Id == media.Id); - } + => mediaService.ExistsAsync(m => m.Type == media.Type && m.Id == media.Id); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/MultiplayerModeImporter.cs b/LANCommander.Server.ImportExport/Importers/MultiplayerModeImporter.cs index f141c449..c0a01518 100644 --- a/LANCommander.Server.ImportExport/Importers/MultiplayerModeImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/MultiplayerModeImporter.cs @@ -1,60 +1,80 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class MultiplayerModeImporter( - IMapper mapper, - MultiplayerModeService multiplayerModeService) : BaseImporter + ILogger logger, + MultiplayerModeService multiplayerModeService, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(MultiplayerMode record) - { - return new ImportItemInfo + public override string GetKey(MultiplayerMode record) + => $"{nameof(MultiplayerMode)}/{record.NetworkProtocol}:{record.Type}"; + + public override async Task> GetImportInfoAsync(MultiplayerMode record) + => new() { Type = ImportExportRecordType.MultiplayerMode, Name = String.IsNullOrWhiteSpace(record.Description) ? record.Type.ToString() : $"{record.Type} - {record.Description}", + Record = record, }; - } - public override bool CanImport(MultiplayerMode record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(MultiplayerMode record) => ImportContext.Manifest is Game; - public override async Task AddAsync(MultiplayerMode record) + public override async Task AddAsync(MultiplayerMode record) { try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + var multiplayerMode = new Data.Models.MultiplayerMode { - Game = ImportContext.DataRecord as Data.Models.Game, Description = record.Description, Type = record.Type, Spectators = record.Spectators, MinPlayers = record.MinPlayers, MaxPlayers = record.MaxPlayers, NetworkProtocol = record.NetworkProtocol, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, + Game = await gameService.GetAsync(game.Id), }; - multiplayerMode = await multiplayerModeService.AddAsync(multiplayerMode); + await multiplayerModeService.AddAsync(multiplayerMode); - return multiplayerMode; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing multiplayer mode", ex); + logger.LogError(ex, "Could not add multiplayer mode | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(MultiplayerMode record) + public override async Task UpdateAsync(MultiplayerMode record) { - var game = ImportContext.DataRecord as Data.Models.Game; - - var existing = await multiplayerModeService.FirstOrDefaultAsync(m => m.GameId == game.Id && m.Type == record.Type); - try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + + var existing = await multiplayerModeService.FirstOrDefaultAsync(m => m.GameId == game.Id && m.Type == record.Type); + existing.Description = record.Description; existing.Type = record.Type; existing.Spectators = record.Spectators; @@ -62,20 +82,22 @@ public class MultiplayerModeImporter( existing.MaxPlayers = record.MaxPlayers; existing.NetworkProtocol = record.NetworkProtocol; - existing = await multiplayerModeService.UpdateAsync(existing); + await multiplayerModeService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing multiplayer mode", ex); + logger.LogError(ex, "Could not update multiplayer mode | {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(MultiplayerMode record) { - var game = ImportContext.DataRecord as Data.Models.Game; - - return await multiplayerModeService.ExistsAsync(m => m.GameId == game.Id && m.Type == record.Type); + if (ImportContext.Manifest is Game game) + return await multiplayerModeService.ExistsAsync(m => m.GameId == game.Id && m.Type == record.Type); + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/PlatformImporter.cs b/LANCommander.Server.ImportExport/Importers/PlatformImporter.cs index 484dc90b..7f5bcfd1 100644 --- a/LANCommander.Server.ImportExport/Importers/PlatformImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/PlatformImporter.cs @@ -1,75 +1,53 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class PlatformImporter( - IMapper mapper, - PlatformService platformService, - GameService gameService) : BaseImporter + ILogger logger, + PlatformService platformService) : BaseImporter { - public override async Task GetImportInfoAsync(Platform record) - { - return new ImportItemInfo + public override string GetKey(Platform record) + => $"{nameof(Platform)}/{record.Name}"; + + public override async Task> GetImportInfoAsync(Platform record) + => new() { Type = ImportExportRecordType.Platform, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Platform record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Platform record) + => await platformService.ExistsAsync(p => p.Name == record.Name); - public override async Task AddAsync(Platform record) + public override async Task AddAsync(Platform record) { try { var platform = new Data.Models.Platform { - Games = new List() { ImportContext.DataRecord as Data.Models.Game }, Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - platform = await platformService.AddAsync(platform); + await platformService.AddAsync(platform); - return platform; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing platform", ex); + logger.LogError(ex, "Failed to add platform | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Platform record) - { - var existing = await platformService.Include(p => p.Games).FirstOrDefaultAsync(c => c.Name == record.Name); - var game = ImportContext.DataRecord as Data.Models.Game; - - try - { - if (existing.Games == null) - existing.Games = new List(); - - if (!existing.Games.Any(g => g.Id == game.Id)) - { - existing.Games.Add(await gameService.GetAsync(game.Id)); - - existing = await platformService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing platform", ex); - } - } + public override async Task UpdateAsync(Platform record) => true; public override async Task ExistsAsync(Platform record) - { - return await platformService.ExistsAsync(c => c.Name == record.Name); - } + => await platformService.ExistsAsync(c => c.Name == record.Name); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/PlaySessionImporter.cs b/LANCommander.Server.ImportExport/Importers/PlaySessionImporter.cs index f638f3f8..1a70a80b 100644 --- a/LANCommander.Server.ImportExport/Importers/PlaySessionImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/PlaySessionImporter.cs @@ -1,77 +1,99 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class PlaySessionImporter( - IMapper mapper, + ILogger logger, PlaySessionService playSessionService, - UserService userService) : BaseImporter + UserService userService, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(PlaySession record) - { - return new ImportItemInfo + public override string GetKey(PlaySession record) + => $"{nameof(PlaySession)}/{record.User}:{record.Start}:{record.End}"; + + public override async Task> GetImportInfoAsync(PlaySession record) + => new() { Type = ImportExportRecordType.PlaySession, Name = $"{record.User} - {record.Start}-{record.End}", + Record = record, }; - } - public override bool CanImport(PlaySession record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(PlaySession record) => ImportContext.Manifest is Game; - public override async Task AddAsync(PlaySession record) + public override async Task AddAsync(PlaySession record) { try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + var playSession = new Data.Models.PlaySession { Start = record.Start, End = record.End, User = await userService.GetAsync(record.User), - Game = ImportContext.DataRecord as Data.Models.Game, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, + Game = await gameService.GetAsync(game.Id), }; - playSession = await playSessionService.AddAsync(playSession); + await playSessionService.AddAsync(playSession); - return playSession; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing playSession", ex); + logger.LogError(ex, "Failed to add play session | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(PlaySession record) + public override async Task UpdateAsync(PlaySession record) { - var game = ImportContext.DataRecord as Data.Models.Game; - var user = await userService.GetAsync(record.User); - - var existing = await playSessionService.FirstOrDefaultAsync(ps => ps.GameId == game.Id && ps.Start == record.Start && ps.UserId == user.Id); - try { + var user = await userService.GetAsync(record.User); + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + var existing = await playSessionService.FirstOrDefaultAsync(ps => ps.GameId == game.Id && ps.Start == record.Start && ps.UserId == user.Id); + existing.Start = record.Start; existing.End = record.End; - existing = await playSessionService.UpdateAsync(existing); + await playSessionService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing playSession", ex); + logger.LogError(ex, "Failed to update play session | {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(PlaySession record) { - var game = ImportContext.DataRecord as Data.Models.Game; - var user = await userService.GetAsync(record.User); - - return await playSessionService.ExistsAsync(ps => (ps.Game.Id == game.Id || ps.Game.Title == game.Title) && ps.Start == record.Start && ps.UserId == user.Id); + if (ImportContext.Manifest is Game game) + { + var user = await userService.GetAsync(record.User); + + return await playSessionService.ExistsAsync(ps => (ps.Game.Id == game.Id || ps.Game.Title == game.Title) && ps.Start == record.Start && ps.UserId == user.Id); + } + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/PublisherImporter.cs b/LANCommander.Server.ImportExport/Importers/PublisherImporter.cs index bb94f937..851280f1 100644 --- a/LANCommander.Server.ImportExport/Importers/PublisherImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/PublisherImporter.cs @@ -1,77 +1,53 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class PublisherImporter( - IMapper mapper, - CompanyService companyService, - GameService gameService) : BaseImporter + ILogger logger, + CompanyService companyService) : BaseImporter { - public override async Task GetImportInfoAsync(Company record) - { - return new ImportItemInfo + public override string GetKey(Company record) + => $"Publisher/{record.Name}"; + + public override async Task> GetImportInfoAsync(Company record) + => new() { Type = ImportExportRecordType.Publisher, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Company record) => ImportContext.DataRecord is Data.Models.Company; + public override async Task CanImportAsync(Company record) + => await companyService.ExistsAsync(c => c.Name == record.Name); - public override async Task AddAsync(Company record) + public override async Task AddAsync(Company record) { try { - var game = ImportContext.DataRecord as Data.Models.Game; - var company = new Data.Models.Company { - PublishedGames = [await gameService.GetAsync(game.Id)], Name = record.Name, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, }; - company = await companyService.AddAsync(company); + await companyService.AddAsync(company); - return company; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing publisher", ex); + logger.LogError(ex, "Could not add company | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Company record) - { - var existing = await companyService.Include(g => g.PublishedGames).FirstOrDefaultAsync(c => c.Name == record.Name); - var game = ImportContext.DataRecord as Data.Models.Game; + public override async Task UpdateAsync(Company record) => true; - try - { - if (existing.PublishedGames == null) - existing.PublishedGames = new List(); - - if (!existing.PublishedGames.Any(g => g.Id == game.Id)) - { - existing.PublishedGames.Add(await gameService.GetAsync(game.Id)); - - existing = await companyService.UpdateAsync(existing); - } - - return existing; - } - catch (Exception ex) - { - throw new ImportSkippedException(record, "An unknown error occured while importing publisher", ex); - } - } - - public override async Task ExistsAsync(Company record) - { - return await companyService.ExistsAsync(c => c.Name == record.Name); - } + 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.Server.ImportExport/Importers/RedistributableImporter.cs b/LANCommander.Server.ImportExport/Importers/RedistributableImporter.cs index b163ff4d..da523fd3 100644 --- a/LANCommander.Server.ImportExport/Importers/RedistributableImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/RedistributableImporter.cs @@ -1,42 +1,49 @@ using AutoMapper; +using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class RedistributableImporter( + ILogger logger, IMapper mapper, RedistributableService redistributableService, - UserService userService) : BaseImporter + UserService userService) : BaseImporter { - public override async Task GetImportInfoAsync(Redistributable record) - { - return new ImportItemInfo() + public override string GetKey(Redistributable record) + => $"{nameof(Redistributable)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(Redistributable record) + => new() { + Type = ImportExportRecordType.Redistributable, Name = record.Name, + Record = record, }; - } - public override bool CanImport(Redistributable record) => true; + public override async Task CanImportAsync(Redistributable record) => true; - public override async Task AddAsync(Redistributable record) + public override async Task AddAsync(Redistributable record) { var redistributable = mapper.Map(record); try { - return await redistributableService.AddAsync(redistributable); + await redistributableService.AddAsync(redistributable); + + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, - "An unknown error occurred while trying to add redistributable", ex); + logger.LogError(ex, "Could not add redistributable | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Redistributable record) + public override async Task UpdateAsync(Redistributable record) { var existing = await redistributableService.FirstOrDefaultAsync(r => r.Id == record.Id || r.Name == record.Name); @@ -50,19 +57,17 @@ public class RedistributableImporter( existing.UpdatedOn = record.UpdatedOn; existing.UpdatedBy = await userService.GetAsync(record.UpdatedBy); - existing = await redistributableService.UpdateAsync(existing); + await redistributableService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, - "An unknown error occurred while trying to add redistributable", ex); + logger.LogError(ex, "Could not update redistributable | {Key}", GetKey(record)); + return false; } } - public override async Task ExistsAsync(Redistributable record) - { - return await redistributableService.ExistsAsync(r => r.Id == record.Id || r.Name == record.Name); - } + public override async Task ExistsAsync(Redistributable record) + => await redistributableService.ExistsAsync(r => r.Id == record.Id || r.Name == record.Name); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/SaveImporter.cs b/LANCommander.Server.ImportExport/Importers/SaveImporter.cs index 91c935ad..d62b7321 100644 --- a/LANCommander.Server.ImportExport/Importers/SaveImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/SaveImporter.cs @@ -1,9 +1,9 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; using SharpCompress.Archives; using SharpCompress.Common; @@ -15,23 +15,27 @@ namespace LANCommander.Server.ImportExport.Importers; /// Valid service provider for injecting the services we need /// The context (archive, parent record> of the import public class SaveImporter( - IMapper mapper, + ILogger logger, UserService userService, - GameSaveService gameSaveService) : BaseImporter + GameSaveService gameSaveService, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(Save record) - { - return new ImportItemInfo + public override string GetKey(Save record) + => $"{nameof(Save)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(Save record) + => new() { Type = ImportExportRecordType.Save, Name = $"{record.User} - {record.CreatedOn}", Size = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Saves/{record.Id}")?.Size ?? 0, + Record = record, }; - } - public override bool CanImport(Save record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(Save record) => ImportContext.Manifest is Game; - public override async Task AddAsync(Save record) + public override async Task AddAsync(Save record) { var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Saves/{record.Id}"); @@ -48,12 +52,21 @@ public class SaveImporter( try { - save = await gameSaveService.AddAsync(new Data.Models.GameSave() + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + + save = await gameSaveService.AddAsync(new Data.Models.GameSave { CreatedBy = user, User = user, CreatedOn = record.CreatedOn, - Game = ImportContext.DataRecord as Data.Models.Game, + UpdatedOn = record.UpdatedOn, + Game = await gameService.GetAsync(game.Id), StorageLocation = await gameSaveService.GetDefaultStorageLocationAsync(), }); @@ -66,18 +79,19 @@ public class SaveImporter( PreserveFileTime = true, }); - return save; + return true; } catch (Exception ex) { if (save != null) await gameSaveService.DeleteAsync(save); - throw new ImportSkippedException(record, "An unknown error occured while importing save file", ex); + logger.LogError(ex, "An error occured while adding save | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(Save record) + public override async Task UpdateAsync(Save record) { var existing = await gameSaveService.FirstOrDefaultAsync(s => s.User.UserName == record.User && s.CreatedOn == record.CreatedOn); @@ -98,18 +112,24 @@ public class SaveImporter( PreserveFileTime = true, }); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing save file", ex); + logger.LogError(ex, "Could not update save file {Key}", GetKey(record)); + return false; } } public override async Task ExistsAsync(Save archive) { - return await gameSaveService - .Include(s => s.User) - .ExistsAsync(s => s.User.UserName == archive.User && s.CreatedOn == archive.CreatedOn && s.GameId == ImportContext.DataRecord.Id); + if (ImportContext.Manifest is Game game) + { + return await gameSaveService + .Include(s => s.User) + .ExistsAsync(s => s.User.UserName == archive.User && s.CreatedOn == archive.CreatedOn && s.GameId == game.Id); + } + + return false; } } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/SavePathImporter.cs b/LANCommander.Server.ImportExport/Importers/SavePathImporter.cs index daf3bbdf..2a087588 100644 --- a/LANCommander.Server.ImportExport/Importers/SavePathImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/SavePathImporter.cs @@ -1,75 +1,87 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; -using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class SavePathImporter( - IMapper mapper, - SavePathService savePathService) : BaseImporter + ILogger logger, + SavePathService savePathService, + GameService gameService, + GameImporter gameImporter) : BaseImporter { - public override async Task GetImportInfoAsync(SavePath record) - { - return new ImportItemInfo + public override string GetKey(SavePath record) + => $"{nameof(SavePath)}/{record.Id}"; + + public override async Task> GetImportInfoAsync(SavePath record) + => new() { Type = ImportExportRecordType.SavePath, Name = record.Path, + Record = record, }; - } - public override bool CanImport(SavePath record) => ImportContext.DataRecord is Data.Models.Game; + public override async Task CanImportAsync(SavePath record) => ImportContext.Manifest is Game; - public override async Task AddAsync(SavePath record) + public override async Task AddAsync(SavePath record) { try { + var game = ImportContext.Manifest as Game; + + if (game == null) + return false; + + if (ImportContext.InQueue(game, gameImporter)) + return false; + var savePath = new Data.Models.SavePath { Id = record.Id, - Game = ImportContext.DataRecord as Data.Models.Game, Path = record.Path, WorkingDirectory = record.WorkingDirectory, IsRegex = record.IsRegex, Type = record.Type, + CreatedOn = record.CreatedOn, + UpdatedOn = record.UpdatedOn, + Game = await gameService.GetAsync(game.Id), }; - savePath = await savePathService.AddAsync(savePath); + await savePathService.AddAsync(savePath); - return savePath; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing save path", ex); + logger.LogError(ex, "Could not add save path | {Key}", GetKey(record)); + return false; } } - public override async Task UpdateAsync(SavePath record) + public override async Task UpdateAsync(SavePath record) { var existing = await savePathService.FirstOrDefaultAsync(p => p.Id == record.Id); try { - existing.Game = ImportContext.DataRecord as Data.Models.Game; existing.Path = record.Path; existing.WorkingDirectory = record.WorkingDirectory; existing.IsRegex = record.IsRegex; existing.Type = record.Type; - existing = await savePathService.UpdateAsync(existing); + await savePathService.UpdateAsync(existing); - return existing; + return true; } catch (Exception ex) { - throw new ImportSkippedException(record, "An unknown error occured while importing save path", ex); + logger.LogError(ex, "Could not update save path | {Key}", GetKey(record)); + return false; } } - public override async Task ExistsAsync(SavePath record) - { - return await savePathService.ExistsAsync(p => p.Id == record.Id); - } + public override async Task ExistsAsync(SavePath record) + => await savePathService.ExistsAsync(p => p.Id == record.Id); } \ No newline at end of file diff --git a/LANCommander.Server.ImportExport/Importers/ScriptImporter.cs b/LANCommander.Server.ImportExport/Importers/ScriptImporter.cs index d428d5c1..f9c1182d 100644 --- a/LANCommander.Server.ImportExport/Importers/ScriptImporter.cs +++ b/LANCommander.Server.ImportExport/Importers/ScriptImporter.cs @@ -1,34 +1,42 @@ -using AutoMapper; using LANCommander.SDK.Enums; using LANCommander.SDK.Models.Manifest; using LANCommander.Server.ImportExport.Exceptions; using LANCommander.Server.ImportExport.Models; using LANCommander.Server.Services; +using Microsoft.Extensions.Logging; namespace LANCommander.Server.ImportExport.Importers; public class ScriptImporter( - IMapper mapper, - ScriptService scriptService) : BaseImporter + ILogger logger, + ScriptService scriptService, + GameService gameService, + RedistributableService redistributableService, + ServerService serverService, + GameImporter gameImporter, + RedistributableImporter redistributableImporter, + ServerImporter serverImporter) : BaseImporter