Refactor server importers

Refactors server importers to better handle dependencies on other data in the queue.
This commit is contained in:
Pat Hartl 2025-12-03 02:22:37 -06:00
parent b4e8925e0e
commit 54400fdb10
32 changed files with 1313 additions and 887 deletions

View file

@ -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<ImportService>();
var context = new ImportContext(serviceProvider);
importService.AddContext(context);
return scope.ServiceProvider.GetRequiredService<ImportContext>();
return context;
}
}

View file

@ -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<ImportContext> 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<IImportItemInfo> Queue { get; } = new();
private IEnumerable<Guid> SelectedRecordIds { get; set; } = [];
public int Remaining => _queue.Count;
public int Processed => _queue.Count(qi => qi.Processed);
public int Total => _queue.Count;
private List<ImportQueueItem> _queue { get; } = new();
private IEnumerable<Guid> _selectedRecordIds { get; set; }
public Dictionary<ImportQueueItem, string> Errored { get; } = new();
public EventHandler<ImportQueueItem> OnRecordAdded;
public EventHandler<ImportQueueItem> OnRecordProcessed;
public EventHandler<ImportQueueItem> 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<ImportStatusUpdate> OnImportStarted { get; set; }
public EventHandler<ImportStatusUpdate> OnImportStatusUpdate { get; set; }
public EventHandler<ImportStatusUpdate> OnImportComplete { get; set; }
public EventHandler<ImportStatusUpdate> OnImportError;
#region Initialize Import
public async Task<IEnumerable<ImportItemInfo>> InitializeImportAsync(string archivePath)
private readonly ImportService _importService;
private readonly StorageLocationService _storageLocationService;
private readonly ILogger<ImportContext> _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<ActionImporter>();
_archives = serviceProvider.GetRequiredService<ArchiveImporter>();
_collections = serviceProvider.GetRequiredService<CollectionImporter>();
_customFields = serviceProvider.GetRequiredService<CustomFieldImporter>();
_developers = serviceProvider.GetRequiredService<DeveloperImporter>();
_engines = serviceProvider.GetRequiredService<EngineImporter>();
_games = serviceProvider.GetRequiredService<GameImporter>();
_genres = serviceProvider.GetRequiredService<GenreImporter>();
_keys = serviceProvider.GetRequiredService<KeyImporter>();
_media = serviceProvider.GetRequiredService<MediaImporter>();
_multiplayerModes = serviceProvider.GetRequiredService<MultiplayerModeImporter>();
_platforms = serviceProvider.GetRequiredService<PlatformImporter>();
_playSessions = serviceProvider.GetRequiredService<PlaySessionImporter>();
_publishers = serviceProvider.GetRequiredService<PublisherImporter>();
_redistributables = serviceProvider.GetRequiredService<RedistributableImporter>();
_saves = serviceProvider.GetRequiredService<SaveImporter>();
_savePaths = serviceProvider.GetRequiredService<SavePathImporter>();
_scripts = serviceProvider.GetRequiredService<ScriptImporter>();
_serverConsoles = serviceProvider.GetRequiredService<ServerConsoleImporter>();
_serverHttpPaths = serviceProvider.GetRequiredService<ServerHttpPathImporter>();
_servers = serviceProvider.GetRequiredService<ServerImporter>();
_tags = serviceProvider.GetRequiredService<TagImporter>();
_importService = serviceProvider.GetRequiredService<ImportService>();
_storageLocationService = serviceProvider.GetRequiredService<StorageLocationService>();
_logger = serviceProvider.GetRequiredService<ILogger<ImportContext>>();
}
internal bool InQueue<TRecord>(TRecord record, BaseImporter<TRecord> 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<IEnumerable<IImportItemInfo>> 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<IEnumerable<ImportItemInfo>> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest)
private async Task<IEnumerable<IImportItemInfo>> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest)
{
Manifest = gameManifest;
var importItemInfo = new List<ImportItemInfo>();
var importItemInfo = new List<IImportItemInfo>();
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<IEnumerable<ImportItemInfo>> InitializeRedistributableImportAsync(SDK.Models.Manifest.Redistributable redistributableManifest)
private async Task<IEnumerable<IImportItemInfo>> InitializeRedistributableImportAsync(SDK.Models.Manifest.Redistributable redistributableManifest)
{
Manifest = redistributableManifest;
var importItemInfo = new List<ImportItemInfo>();
var importItemInfo = new List<IImportItemInfo>();
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<IEnumerable<ImportItemInfo>> InitializeServerImportAsync(SDK.Models.Manifest.Server serverManifest)
private async Task<IEnumerable<IImportItemInfo>> InitializeServerImportAsync(SDK.Models.Manifest.Server serverManifest)
{
Manifest = serverManifest;
var importItemInfo = new List<ImportItemInfo>();
var importItemInfo = new List<IImportItemInfo>();
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<Guid> 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<TRecord>(ImportExportRecordType type, IEnumerable<TRecord> 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<TRecord>(IEnumerable<TRecord> records, BaseImporter<TRecord> importer)
where TRecord : class
{
foreach (var record in records)
await AddAsync(record, importer);
}
private async Task AddAsync<TRecord>(TRecord? record, BaseImporter<TRecord> 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<bool> 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<ImportItemInfo> GetImportItemInfoAsync<TModel, TEntity>(IEnumerable<TModel> records,
BaseImporter<TModel, TEntity> importer)
private async IAsyncEnumerable<ImportItemInfo<TRecord>> GetImportItemInfoAsync<TRecord>(IEnumerable<TRecord> records,
BaseImporter<TRecord> 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<TRecord, TEntity>(ImportQueueItem queueItem, BaseImporter<TRecord, TEntity> 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)

View file

@ -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<Action, Data.Models.Action>
ILogger<ActionImporter> logger,
ActionService actionService,
GameService gameService,
ServerService serverService,
GameImporter gameImporter,
ServerImporter serverImporter) : BaseImporter<Action>
{
public override async Task<ImportItemInfo> 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<ImportItemInfo<Action>> GetImportInfoAsync(Action record)
=> new()
{
Name = record.Name,
Type = ImportExportRecordType.Action,
Record = record,
};
public override async Task<Data.Models.Action> AddAsync(Action record)
public override async Task<bool> CanImportAsync(Action record) => ImportContext.Manifest is Game;
public override async Task<bool> 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<Action>(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<Data.Models.Action> UpdateAsync(Action record)
public override async Task<bool> 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<Action>(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<bool> 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;
}
}

View file

@ -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;
/// <param name="serviceProvider">Valid service provider for injecting the services we need</param>
/// <param name="ImportContext">The context (archive, parent record> of the import</param>
public class ArchiveImporter(
IMapper mapper,
ArchiveService archiveService) : BaseImporter<Archive, Data.Models.Archive>
ILogger<ArchiveImporter> logger,
ArchiveService archiveService,
GameService gameService,
RedistributableService redistributableService) : BaseImporter<Archive>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Archive record)
{
return new ImportItemInfo
public override string GetKey(Archive record)
=> $"{nameof(Archive)}/{record.Id}";
public override async Task<ImportItemInfo<Archive>> 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<bool> CanImportAsync(Archive record) => ImportContext.Manifest is Game || ImportContext.Manifest is Redistributable;
public override async Task<Data.Models.Archive> AddAsync(Archive record)
public override async Task<bool> 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<Archive>(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<Archive>(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<Data.Models.Archive> UpdateAsync(Archive archive)
public override async Task<bool> 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>(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<bool> 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>(archive, $"Cannot import an archive for a {ImportContext.DataRecord.GetType().Name}");
throw new ImportSkippedException<Archive>(archive, $"Cannot import archive, incompatible manifest");
}
}

View file

@ -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<Collection, Data.Models.Collection>
ILogger<CollectionImporter> logger,
CollectionService collectionService) : BaseImporter<Collection>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Collection record)
{
return new ImportItemInfo
public override string GetKey(Collection record)
=> $"{nameof(Collection)}/{record.Name}";
public override async Task<ImportItemInfo<Collection>> 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<bool> CanImportAsync(Collection record)
=> await collectionService.ExistsAsync(c => c.Name == record.Name);
public override async Task<Data.Models.Collection> AddAsync(Collection record)
public override async Task<bool> AddAsync(Collection record)
{
try
{
var collection = new Data.Models.Collection
{
Games = new List<Data.Models.Game>() { 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<Collection>(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<Data.Models.Collection> 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<Data.Models.Game>();
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<Collection>(record, "An unknown error occured while importing collection", ex);
}
}
public override async Task<bool> UpdateAsync(Collection record) => true;
public override async Task<bool> ExistsAsync(Collection record)
{
return await collectionService.ExistsAsync(c => c.Name == record.Name);
}
=> await collectionService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -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<GameCustomField, Data.Models.GameCustomField>
ILogger<CustomFieldImporter> logger,
GameService gameService,
GameImporter gameImporter) : BaseImporter<GameCustomField>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(GameCustomField record)
{
return new ImportItemInfo
public override string GetKey(GameCustomField record)
=> $"{nameof(GameCustomField)}/{record.Name}";
public override async Task<ImportItemInfo<GameCustomField>> 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<Data.Models.GameCustomField> AddAsync(GameCustomField record)
public override async Task<bool> CanImportAsync(GameCustomField record) => ImportContext.Manifest is Game;
public override async Task<bool> 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<GameCustomField>(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<Data.Models.GameCustomField> UpdateAsync(GameCustomField record)
public override async Task<bool> 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<GameCustomField>(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<bool> 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;
}
}

View file

@ -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<DeveloperImporter> logger,
CompanyService companyService,
GameService gameService) : BaseImporter<Company, Data.Models.Company>
GameService gameService) : BaseImporter<Company>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Company record)
{
return new ImportItemInfo
public override string GetKey(Company record)
=> $"Developer/{record.Name}";
public override async Task<ImportItemInfo<Company>> 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<bool> CanImportAsync(Company record)
=> await companyService.ExistsAsync(c => c.Name == record.Name);
public override async Task<Data.Models.Company> AddAsync(Company record)
public override async Task<bool> AddAsync(Company record)
{
if (ImportContext.DataRecord is not Data.Models.Game)
throw new ImportSkippedException<Company>(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<Company>(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<Data.Models.Company> UpdateAsync(Company record)
{
if (ImportContext.DataRecord is not Data.Models.Game)
throw new ImportSkippedException<Company>(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<Data.Models.Game>();
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<Company>(record, "An unknown error occured while importing developer", ex);
}
}
public override async Task<bool> UpdateAsync(Company record) => true;
public override async Task<bool> ExistsAsync(Company record)
{
if (ImportContext.DataRecord is not Data.Models.Game game)
throw new ImportSkippedException<Company>(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);
}

View file

@ -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<Engine, Data.Models.Engine>
ILogger<EngineImporter> logger,
EngineService engineService) : BaseImporter<Engine>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Engine record)
{
return new ImportItemInfo
public override string GetKey(Engine record)
=> $"{nameof(Engine)}/{record.Name}";
public override async Task<ImportItemInfo<Engine>> 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<bool> CanImportAsync(Engine record)
=> await engineService.ExistsAsync(e => e.Name == record.Name);
public override async Task<Data.Models.Engine> AddAsync(Engine record)
public override async Task<bool> AddAsync(Engine record)
{
try
{
var engine = new Data.Models.Engine
{
Games = new List<Data.Models.Game>() { 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<Engine>(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<Data.Models.Engine> 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<Data.Models.Game>();
public override async Task<bool> 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<Engine>(record, "An unknown error occured while importing engine", ex);
}
}
public override async Task<bool> ExistsAsync(Engine record)
{
return await engineService.ExistsAsync(c => c.Name == record.Name);
}
public override async Task<bool> ExistsAsync(Engine record)
=> await engineService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -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<GameImporter> logger,
GameService gameService,
UserService userService) : BaseImporter<Game, Data.Models.Game>
UserService userService) : BaseImporter<Game>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Game record)
public override string GetKey(Game record)
=> $"{nameof(Game)}/{record.Id}";
public override async Task<ImportItemInfo<Game>> GetImportInfoAsync(Game record)
{
return new ImportItemInfo
return new ImportItemInfo<Game>
{
Type = ImportExportRecordType.Game,
Name = record.Title,
Record = record,
};
}
public override bool CanImport(Game record) => true;
public override async Task<bool> CanImportAsync(Game record) => true;
public override async Task<Data.Models.Game> AddAsync(Game record)
public override async Task<bool> 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<Game>(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<Data.Models.Game> UpdateAsync(Game record)
public override async Task<bool> 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<Game>(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;
});
}
}

View file

@ -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<Genre, Data.Models.Genre>
ILogger<GenreImporter> logger,
GenreService genreService) : BaseImporter<Genre>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Genre record)
public override string GetKey(Genre record)
=> $"{nameof(Genre)}/{record.Name}";
public override async Task<ImportItemInfo<Genre>> GetImportInfoAsync(Genre record)
{
return new ImportItemInfo
return new ImportItemInfo<Genre>
{
Type = ImportExportRecordType.Genre,
Name = record.Name,
Record = record,
};
}
public override bool CanImport(Genre record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<bool> CanImportAsync(Genre record)
=> await genreService.ExistsAsync(g => g.Name == record.Name);
public override async Task<Data.Models.Genre> AddAsync(Genre record)
public override async Task<bool> AddAsync(Genre record)
{
try
{
var genre = new Data.Models.Genre
{
Games = new List<Data.Models.Game>() { 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<Genre>(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<Data.Models.Genre> 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<Data.Models.Game>();
public override async Task<bool> 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<Genre>(record, "An unknown error occured while importing genre", ex);
}
}
public override async Task<bool> ExistsAsync(Genre record)
{
return await genreService.ExistsAsync(c => c.Name == record.Name);
}
public override async Task<bool> ExistsAsync(Genre record)
=> await genreService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -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<Key, Data.Models.Key>
ILogger<KeyImporter> logger,
KeyService keyService,
GameImporter gameImporter,
GameService gameService) : BaseImporter<Key>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Key record)
public override string GetKey(Key record)
=> $"{nameof(Key)}/{record.Value}";
public override async Task<ImportItemInfo<Key>> GetImportInfoAsync(Key record)
{
return new ImportItemInfo
return new ImportItemInfo<Key>
{
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<bool> CanImportAsync(Key record)
=> await keyService.ExistsAsync(k => k.Value == record.Value);
public override async Task<Data.Models.Key> AddAsync(Key record)
public override async Task<bool> 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<Key>(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<Data.Models.Key> UpdateAsync(Key record)
public override async Task<bool> 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<Key>(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<bool> 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;
}
}

View file

@ -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<MediaImporter> logger,
StorageLocationService storageLocationService,
MediaService mediaService) : BaseImporter<Media, Data.Models.Media>
MediaService mediaService,
GameService gameService,
GameImporter gameImporter) : BaseImporter<Media>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Media record)
public override string GetKey(Media record)
=> $"{nameof(Media)}/{record.Id}";
public override async Task<ImportItemInfo<Media>> GetImportInfoAsync(Media record)
{
return new ImportItemInfo
return new ImportItemInfo<Media>
{
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<bool> CanImportAsync(Media record) => ImportContext.Manifest is Game;
public override async Task<Data.Models.Media> AddAsync(Media record)
public override async Task<bool> 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<Media>(record, "An unknown error occured while trying to import media file",
ex);
return false;
}
}
public override async Task<Data.Models.Media> UpdateAsync(Media record)
public override async Task<bool> 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<Media>(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<bool> 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);
}

View file

@ -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<MultiplayerMode, Data.Models.MultiplayerMode>
ILogger<MultiplayerModeImporter> logger,
MultiplayerModeService multiplayerModeService,
GameService gameService,
GameImporter gameImporter) : BaseImporter<MultiplayerMode>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(MultiplayerMode record)
{
return new ImportItemInfo
public override string GetKey(MultiplayerMode record)
=> $"{nameof(MultiplayerMode)}/{record.NetworkProtocol}:{record.Type}";
public override async Task<ImportItemInfo<MultiplayerMode>> 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<bool> CanImportAsync(MultiplayerMode record) => ImportContext.Manifest is Game;
public override async Task<Data.Models.MultiplayerMode> AddAsync(MultiplayerMode record)
public override async Task<bool> 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<MultiplayerMode>(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<Data.Models.MultiplayerMode> UpdateAsync(MultiplayerMode record)
public override async Task<bool> 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<MultiplayerMode>(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<bool> 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;
}
}

View file

@ -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<Platform, Data.Models.Platform>
ILogger<PlatformImporter> logger,
PlatformService platformService) : BaseImporter<Platform>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Platform record)
{
return new ImportItemInfo
public override string GetKey(Platform record)
=> $"{nameof(Platform)}/{record.Name}";
public override async Task<ImportItemInfo<Platform>> 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<bool> CanImportAsync(Platform record)
=> await platformService.ExistsAsync(p => p.Name == record.Name);
public override async Task<Data.Models.Platform> AddAsync(Platform record)
public override async Task<bool> AddAsync(Platform record)
{
try
{
var platform = new Data.Models.Platform
{
Games = new List<Data.Models.Game>() { 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<Platform>(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<Data.Models.Platform> 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<Data.Models.Game>();
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<Platform>(record, "An unknown error occured while importing platform", ex);
}
}
public override async Task<bool> UpdateAsync(Platform record) => true;
public override async Task<bool> ExistsAsync(Platform record)
{
return await platformService.ExistsAsync(c => c.Name == record.Name);
}
=> await platformService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -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<PlaySessionImporter> logger,
PlaySessionService playSessionService,
UserService userService) : BaseImporter<PlaySession, Data.Models.PlaySession>
UserService userService,
GameService gameService,
GameImporter gameImporter) : BaseImporter<PlaySession>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(PlaySession record)
{
return new ImportItemInfo
public override string GetKey(PlaySession record)
=> $"{nameof(PlaySession)}/{record.User}:{record.Start}:{record.End}";
public override async Task<ImportItemInfo<PlaySession>> 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<bool> CanImportAsync(PlaySession record) => ImportContext.Manifest is Game;
public override async Task<Data.Models.PlaySession> AddAsync(PlaySession record)
public override async Task<bool> 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<PlaySession>(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<Data.Models.PlaySession> UpdateAsync(PlaySession record)
public override async Task<bool> 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<PlaySession>(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<bool> 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;
}
}

View file

@ -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<Company, Data.Models.Company>
ILogger<PublisherImporter> logger,
CompanyService companyService) : BaseImporter<Company>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Company record)
{
return new ImportItemInfo
public override string GetKey(Company record)
=> $"Publisher/{record.Name}";
public override async Task<ImportItemInfo<Company>> 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<bool> CanImportAsync(Company record)
=> await companyService.ExistsAsync(c => c.Name == record.Name);
public override async Task<Data.Models.Company> AddAsync(Company record)
public override async Task<bool> 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<Company>(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<Data.Models.Company> 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<bool> UpdateAsync(Company record) => true;
try
{
if (existing.PublishedGames == null)
existing.PublishedGames = new List<Data.Models.Game>();
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<Company>(record, "An unknown error occured while importing publisher", ex);
}
}
public override async Task<bool> ExistsAsync(Company record)
{
return await companyService.ExistsAsync(c => c.Name == record.Name);
}
public override async Task<bool> ExistsAsync(Company record)
=> await companyService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -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<RedistributableImporter> logger,
IMapper mapper,
RedistributableService redistributableService,
UserService userService) : BaseImporter<Redistributable, Data.Models.Redistributable>
UserService userService) : BaseImporter<Redistributable>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Redistributable record)
{
return new ImportItemInfo()
public override string GetKey(Redistributable record)
=> $"{nameof(Redistributable)}/{record.Id}";
public override async Task<ImportItemInfo<Redistributable>> GetImportInfoAsync(Redistributable record)
=> new()
{
Type = ImportExportRecordType.Redistributable,
Name = record.Name,
Record = record,
};
}
public override bool CanImport(Redistributable record) => true;
public override async Task<bool> CanImportAsync(Redistributable record) => true;
public override async Task<Data.Models.Redistributable> AddAsync(Redistributable record)
public override async Task<bool> AddAsync(Redistributable record)
{
var redistributable = mapper.Map<Data.Models.Redistributable>(record);
try
{
return await redistributableService.AddAsync(redistributable);
await redistributableService.AddAsync(redistributable);
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<Redistributable>(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<Data.Models.Redistributable> UpdateAsync(Redistributable record)
public override async Task<bool> 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<Redistributable>(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<bool> ExistsAsync(Redistributable record)
{
return await redistributableService.ExistsAsync(r => r.Id == record.Id || r.Name == record.Name);
}
public override async Task<bool> ExistsAsync(Redistributable record)
=> await redistributableService.ExistsAsync(r => r.Id == record.Id || r.Name == record.Name);
}

View file

@ -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;
/// <param name="serviceProvider">Valid service provider for injecting the services we need</param>
/// <param name="ImportContext">The context (archive, parent record> of the import</param>
public class SaveImporter(
IMapper mapper,
ILogger<SaveImporter> logger,
UserService userService,
GameSaveService gameSaveService) : BaseImporter<Save, Data.Models.GameSave>
GameSaveService gameSaveService,
GameService gameService,
GameImporter gameImporter) : BaseImporter<Save>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Save record)
{
return new ImportItemInfo
public override string GetKey(Save record)
=> $"{nameof(Save)}/{record.Id}";
public override async Task<ImportItemInfo<Save>> 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<bool> CanImportAsync(Save record) => ImportContext.Manifest is Game;
public override async Task<Data.Models.GameSave> AddAsync(Save record)
public override async Task<bool> 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<Save>(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<Data.Models.GameSave> UpdateAsync(Save record)
public override async Task<bool> 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<Save>(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<bool> 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;
}
}

View file

@ -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<SavePath, Data.Models.SavePath>
ILogger<SavePathImporter> logger,
SavePathService savePathService,
GameService gameService,
GameImporter gameImporter) : BaseImporter<SavePath>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(SavePath record)
{
return new ImportItemInfo
public override string GetKey(SavePath record)
=> $"{nameof(SavePath)}/{record.Id}";
public override async Task<ImportItemInfo<SavePath>> 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<bool> CanImportAsync(SavePath record) => ImportContext.Manifest is Game;
public override async Task<Data.Models.SavePath> AddAsync(SavePath record)
public override async Task<bool> 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<SavePath>(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<Data.Models.SavePath> UpdateAsync(SavePath record)
public override async Task<bool> 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<SavePath>(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<bool> ExistsAsync(SavePath record)
{
return await savePathService.ExistsAsync(p => p.Id == record.Id);
}
public override async Task<bool> ExistsAsync(SavePath record)
=> await savePathService.ExistsAsync(p => p.Id == record.Id);
}

View file

@ -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<Script, Data.Models.Script>
ILogger<ScriptImporter> logger,
ScriptService scriptService,
GameService gameService,
RedistributableService redistributableService,
ServerService serverService,
GameImporter gameImporter,
RedistributableImporter redistributableImporter,
ServerImporter serverImporter) : BaseImporter<Script>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Script record)
{
return new ImportItemInfo
public override string GetKey(Script record)
=> $"{nameof(Script)}/{record.Id}";
public override async Task<ImportItemInfo<Script>> GetImportInfoAsync(Script record)
=> new()
{
Type = ImportExportRecordType.Script,
Name = record.Name,
Size = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Scripts/{record.Id}")?.Size ?? 0,
Record = record,
};
}
public override bool CanImport(Script record) =>
ImportContext.DataRecord is Data.Models.Game
public override async Task<bool> CanImportAsync(Script record) =>
ImportContext.Manifest is Game
||
ImportContext.DataRecord is Data.Models.Redistributable
ImportContext.Manifest is Redistributable
||
ImportContext.DataRecord is Data.Models.Server;
ImportContext.Manifest is SDK.Models.Manifest.Server;
public override async Task<Data.Models.Script> AddAsync(Script record)
public override async Task<bool> AddAsync(Script record)
{
var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Scripts/{record.Id}");
@ -43,50 +51,57 @@ public class ScriptImporter(
var newScript = new Data.Models.Script
{
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
Name = record.Name,
Description = record.Description,
RequiresAdmin = record.RequiresAdmin,
Type = record.Type,
};
if (ImportContext.DataRecord is Data.Models.Game game)
newScript.Game = game;
else if (ImportContext.DataRecord is Data.Models.Redistributable redistributable)
newScript.Redistributable = redistributable;
else if (ImportContext.DataRecord is Data.Models.Server server)
newScript.Server = server;
if (ImportContext.Manifest is Game game)
newScript.Game = await gameService.GetAsync(game.Id);
else if (ImportContext.Manifest is Redistributable redistributable)
newScript.Redistributable = await redistributableService.GetAsync(redistributable.Id);
else if (ImportContext.Manifest is SDK.Models.Manifest.Server server)
newScript.Server = await serverService.GetAsync(server.Id);
else
return false;
using (var streamReader = new StreamReader(archiveEntry.OpenEntryStream()))
{
newScript.Contents = await streamReader.ReadToEndAsync();
}
script = await scriptService.AddAsync(newScript);
await scriptService.AddAsync(newScript);
return script;
return true;
}
catch (Exception ex)
{
if (script != null)
await scriptService.DeleteAsync(script);
throw new ImportSkippedException<Script>(record, "An unknown error occured while importing script", ex);
logger.LogError(ex, "Failed to add script | {Key}", GetKey(record));
return false;
}
}
public override async Task<Data.Models.Script> UpdateAsync(Script record)
public override async Task<bool> UpdateAsync(Script record)
{
var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Scripts/{record.Id}");
Data.Models.Script existing = null;
if (ImportContext.DataRecord is Data.Models.Game game)
if (ImportContext.Manifest is Game game)
existing = await scriptService.FirstOrDefaultAsync(s => s.Type == record.Type && s.Name == record.Name && s.GameId == game.Id);
else if (ImportContext.DataRecord is Data.Models.Redistributable redistributable)
else if (ImportContext.Manifest is Redistributable redistributable)
existing = await scriptService.FirstOrDefaultAsync(s => s.Type == record.Type && s.Name == record.Name && s.RedistributableId == redistributable.Id);
else if (ImportContext.DataRecord is Data.Models.Server server)
else if (ImportContext.Manifest is SDK.Models.Manifest.Server server)
existing = await scriptService.FirstOrDefaultAsync(s => s.Type == record.Type && s.Name == record.Name && s.ServerId == server.Id);
if (existing == null)
return false;
try
{
existing.CreatedOn = record.CreatedOn;
@ -100,25 +115,26 @@ public class ScriptImporter(
existing.Contents = await streamReader.ReadToEndAsync();
}
existing = await scriptService.UpdateAsync(existing);
await scriptService.UpdateAsync(existing);
return existing;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<Script>(record, "An unknown error occured while importing script", ex);
logger.LogError(ex, "Failed to update script | {Key}", GetKey(record));
return false;
}
}
public override async Task<bool> ExistsAsync(Script record)
{
if (ImportContext.DataRecord is Data.Models.Game game)
if (ImportContext.Manifest is Game game)
return await scriptService.ExistsAsync(s => s.Type == record.Type && s.Name == record.Name && s.GameId == game.Id);
if (ImportContext.DataRecord is Data.Models.Redistributable redistributable)
if (ImportContext.Manifest is Redistributable redistributable)
return await scriptService.ExistsAsync(s => s.Type == record.Type && s.Name == record.Name && s.RedistributableId == redistributable.Id);
if (ImportContext.DataRecord is Data.Models.Server server)
if (ImportContext.Manifest is SDK.Models.Manifest.Server server)
return await scriptService.ExistsAsync(s => s.Type == record.Type && s.Name == record.Name && s.ServerId == server.Id);
return false;

View file

@ -1,31 +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 ServerConsoleImporter(
IMapper mapper,
ServerConsoleService serverConsoleService) : BaseImporter<ServerConsole, Data.Models.ServerConsole>
ILogger<ServerConsoleImporter> logger,
ServerConsoleService serverConsoleService,
ServerService serverService,
ServerImporter serverImporter) : BaseImporter<ServerConsole>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(ServerConsole record)
{
return new ImportItemInfo
public override string GetKey(ServerConsole record)
=> $"{nameof(ServerConsole)}/{record.Name}";
public override async Task<ImportItemInfo<ServerConsole>> GetImportInfoAsync(ServerConsole record)
=> new()
{
Type = ImportExportRecordType.ServerConsole,
Name = record.Name,
Record = record,
};
}
public override bool CanImport(ServerConsole record) => ImportContext.DataRecord is Data.Models.ServerConsole;
public override async Task<bool> CanImportAsync(ServerConsole record) => ImportContext.Manifest is SDK.Models.Manifest.Server;
public override async Task<Data.Models.ServerConsole> AddAsync(ServerConsole record)
public override async Task<bool> AddAsync(ServerConsole record)
{
try
{
var server = ImportContext.Manifest as SDK.Models.Manifest.Server;
if (server == null)
return false;
if (ImportContext.InQueue(server, serverImporter))
return false;
var serverConsole = new Data.Models.ServerConsole
{
Name = record.Name,
@ -33,47 +44,57 @@ public class ServerConsoleImporter(
Path = record.Path,
Host = record.Host,
Port = record.Port,
Server = ImportContext.DataRecord as Data.Models.Server,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
Server = await serverService.GetAsync(server.Id),
};
serverConsole = await serverConsoleService.AddAsync(serverConsole);
await serverConsoleService.AddAsync(serverConsole);
return serverConsole;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<ServerConsole>(record, "An unknown error occured while importing server console", ex);
logger.LogError(ex, "Could not add server console | {Key}", GetKey(record));
return false;
}
}
public override async Task<Data.Models.ServerConsole> UpdateAsync(ServerConsole record)
public override async Task<bool> UpdateAsync(ServerConsole record)
{
var existing = await serverConsoleService
.Include(c => c.Server)
.FirstOrDefaultAsync(c => c.Name == record.Name && c.Server.Name == (ImportContext.DataRecord as Data.Models.Server).Name);
try
{
var server = ImportContext.Manifest as SDK.Models.Manifest.Server;
if (server == null)
return false;
var existing = await serverConsoleService.FirstOrDefaultAsync(c => c.Name == record.Name && c.ServerId == server.Id);
existing.Name = record.Name;
existing.Type = record.Type;
existing.Path = record.Path;
existing.Host = record.Host;
existing.Port = record.Port;
existing = await serverConsoleService.UpdateAsync(existing);
await serverConsoleService.UpdateAsync(existing);
return existing;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<ServerConsole>(record, "An unknown error occured while importing server console", ex);
logger.LogError(ex, "Could not update server console | {Key}", GetKey(record));
return false;
}
}
public override async Task<bool> ExistsAsync(ServerConsole record)
{
return await serverConsoleService
.Include(c => c.Server)
.ExistsAsync(c => c.Name == record.Name && c.Server.Name == (ImportContext.DataRecord as Data.Models.Server).Name);
if (ImportContext.Manifest is SDK.Models.Manifest.Server server)
return await serverConsoleService
.Include(c => c.Server)
.ExistsAsync(c => c.Name == record.Name && c.ServerId == server.Id);
return false;
}
}

View file

@ -1,75 +1,95 @@
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 ServerHttpPathImporter(
IMapper mapper,
ILogger<ServerHttpPathImporter> logger,
ServerHttpPathService serverHttpPathService,
ServerService serverService) : BaseImporter<ServerHttpPath, Data.Models.ServerHttpPath>
ServerService serverService,
ServerImporter serverImporter) : BaseImporter<ServerHttpPath>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(ServerHttpPath record)
{
return new ImportItemInfo
public override string GetKey(ServerHttpPath record)
=> $"{nameof(ServerHttpPath)}/{record.LocalPath}";
public override async Task<ImportItemInfo<ServerHttpPath>> GetImportInfoAsync(ServerHttpPath record)
=> new()
{
Type = ImportExportRecordType.ServerHttpPath,
Name = record.Path,
Record = record,
};
}
public override bool CanImport(ServerHttpPath record) => ImportContext.DataRecord is Data.Models.Server;
public override async Task<bool> CanImportAsync(ServerHttpPath record) => ImportContext.Manifest is SDK.Models.Manifest.Server;
public override async Task<Data.Models.ServerHttpPath> AddAsync(ServerHttpPath record)
public override async Task<bool> AddAsync(ServerHttpPath record)
{
try
{
var server = ImportContext.Manifest as SDK.Models.Manifest.Server;
if (server == null)
return false;
if (ImportContext.InQueue(server, serverImporter))
return false;
var serverHttpPath = new Data.Models.ServerHttpPath
{
LocalPath = record.LocalPath,
Path = record.Path,
Server = await serverService.FirstOrDefaultAsync(s => s.Name == (ImportContext.DataRecord as Data.Models.Server).Name),
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
Server = await serverService.GetAsync(server.Id),
};
serverHttpPath = await serverHttpPathService.AddAsync(serverHttpPath);
await serverHttpPathService.AddAsync(serverHttpPath);
return serverHttpPath;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<ServerHttpPath>(record, "An unknown error occured while importing server console", ex);
logger.LogError(ex, "Could not add server HTTP path | {Key}", GetKey(record));
return false;
}
}
public override async Task<Data.Models.ServerHttpPath> UpdateAsync(ServerHttpPath record)
public override async Task<bool> UpdateAsync(ServerHttpPath record)
{
var existing = await serverHttpPathService.FirstOrDefaultAsync(p => p.Path == record.Path);
try
{
var server = ImportContext.Manifest as SDK.Models.Manifest.Server;
if (server == null)
return false;
existing.LocalPath = record.LocalPath;
existing.Path = record.Path;
existing.Server =
await serverService.FirstOrDefaultAsync(
s => s.Name == (ImportContext.DataRecord as Data.Models.Server).Name);
existing.Server = await serverService.GetAsync(server.Id);
existing = await serverHttpPathService.UpdateAsync(existing);
await serverHttpPathService.UpdateAsync(existing);
return existing;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<ServerHttpPath>(record, "An unknown error occured while importing server console", ex);
logger.LogError(ex, "Could not update server HTTP path | {Key}", GetKey(record));
return false;
}
}
public override async Task<bool> ExistsAsync(ServerHttpPath record)
{
return await serverHttpPathService
.Include(p => p.Server)
.ExistsAsync(p => p.Path == record.Path && p.Server.Name == (ImportContext.DataRecord as Data.Models.Server).Name);
if (ImportContext.Manifest is SDK.Models.Manifest.Server server)
return await serverHttpPathService
.Include(p => p.Server)
.ExistsAsync(p => p.Path == record.Path && p.ServerId == server.Id);
return false;
}
}

View file

@ -1,48 +1,57 @@
using AutoMapper;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.SDK.Enums;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using Microsoft.Extensions.Logging;
using SharpCompress.Archives;
using SharpCompress.Common;
namespace LANCommander.Server.ImportExport.Importers;
public class ServerImporter(
ILogger<ServerImporter> logger,
IMapper mapper,
ServerService serverService,
GameService gameService,
UserService userService) : BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server>
UserService userService) : BaseImporter<SDK.Models.Manifest.Server>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(SDK.Models.Manifest.Server record)
public override string GetKey(SDK.Models.Manifest.Server record)
=> $"{nameof(SDK.Models.Manifest.Server)}/{record.Id}";
public override async Task<ImportItemInfo<SDK.Models.Manifest.Server>> GetImportInfoAsync(SDK.Models.Manifest.Server record)
{
var fileEntries = ImportContext.Archive.Entries.Where(e => e.Key.StartsWith("Files/"));
return new ImportItemInfo
return new ImportItemInfo<SDK.Models.Manifest.Server>
{
Type = ImportExportRecordType.Server,
Name = record.Name,
Size = fileEntries.Sum(f => f.Size),
Record = record,
};
}
public override bool CanImport(SDK.Models.Manifest.Server record) => true;
public override async Task<bool> CanImportAsync(SDK.Models.Manifest.Server record) => true;
public override async Task<Data.Models.Server> AddAsync(SDK.Models.Manifest.Server record)
public override async Task<bool> AddAsync(SDK.Models.Manifest.Server record)
{
var server = mapper.Map<Data.Models.Server>(record);
try
{
await ExtractFiles(server);
return await serverService.AddAsync(server);
await serverService.AddAsync(server);
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<SDK.Models.Manifest.Server>(record, "An unknown error occured while trying to add server", ex);
logger.LogError(ex, "Could not add server");
return false;
}
}
public override async Task<Data.Models.Server> UpdateAsync(SDK.Models.Manifest.Server record)
public override async Task<bool> UpdateAsync(SDK.Models.Manifest.Server record)
{
var existing = await serverService.FirstOrDefaultAsync(s => s.Id == record.Id || s.Name == record.Name);
@ -71,11 +80,12 @@ public class ServerImporter(
await ExtractFiles(existing);
return existing;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<SDK.Models.Manifest.Server>(record, "An unknown error occurred while trying to update server", ex);
logger.LogError(ex, "Could not update server");
return false;
}
}

View file

@ -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 TagImporter(
IMapper mapper,
TagService tagService,
GameService gameService) : BaseImporter<Tag, Data.Models.Tag>
ILogger<TagImporter> logger,
TagService tagService) : BaseImporter<Tag>
{
public override async Task<ImportItemInfo> GetImportInfoAsync(Tag record)
{
return new ImportItemInfo
public override string GetKey(Tag record)
=> $"{nameof(Tag)}/{record.Name}";
public override async Task<ImportItemInfo<Tag>> GetImportInfoAsync(Tag record)
=> new()
{
Type = ImportExportRecordType.Tag,
Name = record.Name,
Record = record,
};
}
public override bool CanImport(Tag record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<bool> CanImportAsync(Tag record)
=> await tagService.ExistsAsync(t => t.Name == record.Name);
public override async Task<Data.Models.Tag> AddAsync(Tag record)
public override async Task<bool> AddAsync(Tag record)
{
try
{
var tag = new Data.Models.Tag
{
Games = new List<Data.Models.Game>() { ImportContext.DataRecord as Data.Models.Game },
Name = record.Name,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
};
tag = await tagService.AddAsync(tag);
await tagService.AddAsync(tag);
return tag;
return true;
}
catch (Exception ex)
{
throw new ImportSkippedException<Tag>(record, "An unknown error occured while importing tag", ex);
logger.LogError(ex, "Could not add tag | {Key}", GetKey(record));
return false;
}
}
public override async Task<Data.Models.Tag> UpdateAsync(Tag record)
{
var existing = await tagService.Include(t => t.Games).FirstOrDefaultAsync(c => c.Name == record.Name);
var game = ImportContext.DataRecord as Data.Models.Game;
try
{
if (existing.Games == null)
existing.Games = new List<Data.Models.Game>();
public override async Task<bool> UpdateAsync(Tag record) => true;
if (!existing.Games.Any(g => g.Id == game.Id))
{
existing.Games.Add(await gameService.GetAsync(game.Id));
existing = await tagService.UpdateAsync(existing);
}
return existing;
}
catch (Exception ex)
{
throw new ImportSkippedException<Tag>(record, "An unknown error occured while importing tag", ex);
}
}
public override async Task<bool> ExistsAsync(Tag record)
{
return await tagService.ExistsAsync(c => c.Name == record.Name);
}
public override async Task<bool> ExistsAsync(Tag record)
=> await tagService.ExistsAsync(c => c.Name == record.Name);
}

View file

@ -1,8 +1,9 @@
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.ImportExport.Importers;
public abstract class BaseImporter<TRecord, TEntity> : IImporter<TRecord, TEntity>
public abstract class BaseImporter<TRecord> : IImporter<TRecord> where TRecord : class
{
protected ImportContext ImportContext { get; private set; }
@ -10,10 +11,28 @@ public abstract class BaseImporter<TRecord, TEntity> : IImporter<TRecord, TEntit
{
ImportContext = context;
}
public async Task<bool> ImportAsync(IImportItemInfo importItem)
{
if (importItem is ImportItemInfo<TRecord> importItemInfo)
{
if (await ExistsAsync(importItemInfo.Record))
{
importItem.Processed = true;
return await UpdateAsync(importItemInfo.Record);
}
importItem.Processed = true;
return await AddAsync(importItemInfo.Record);
}
throw new ImportSkippedException<TRecord>(null, "Import item record is not supported by this importer.");
}
public abstract Task<ImportItemInfo> GetImportInfoAsync(TRecord record);
public abstract bool CanImport(TRecord record);
public abstract Task<TEntity> AddAsync(TRecord record);
public abstract Task<TEntity> UpdateAsync(TRecord record);
public abstract string GetKey(TRecord record);
public abstract Task<ImportItemInfo<TRecord>> GetImportInfoAsync(TRecord record);
public abstract Task<bool> CanImportAsync(TRecord record);
public abstract Task<bool> AddAsync(TRecord record);
public abstract Task<bool> UpdateAsync(TRecord record);
public abstract Task<bool> ExistsAsync(TRecord record);
}

View file

@ -8,12 +8,13 @@ namespace LANCommander.Server.ImportExport.Importers;
/// this needs to exist for each type.
/// </summary>
/// <typeparam name="TRecord"></typeparam>
public interface IImporter<TRecord, TEntity>
public interface IImporter<TRecord> where TRecord : class
{
void UseContext(ImportContext context);
Task<ImportItemInfo> GetImportInfoAsync(TRecord record);
bool CanImport(TRecord record);
Task<TEntity> AddAsync(TRecord record);
Task<TEntity> UpdateAsync(TRecord record);
Task<ImportItemInfo<TRecord>> GetImportInfoAsync(TRecord record);
Task<bool> CanImportAsync(TRecord record);
Task<bool> ImportAsync(IImportItemInfo importItem);
Task<bool> AddAsync(TRecord record);
Task<bool> UpdateAsync(TRecord record);
Task<bool> ExistsAsync(TRecord record);
}

View file

@ -0,0 +1,12 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.ImportExport.Models;
public interface IImportItemInfo
{
string Key { get; set; }
ImportExportRecordType Type { get; set; }
string Name { get; set; }
bool Processed { get; set; }
long Size { get; set; }
}

View file

@ -2,10 +2,12 @@ using LANCommander.SDK.Enums;
namespace LANCommander.Server.ImportExport.Models;
public class ImportItemInfo
public class ImportItemInfo<T> : IImportItemInfo where T : class
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Key { get; set; }
public ImportExportRecordType Type { get; set; }
public string Name { get; set; }
public bool Processed { get; set; }
public long Size { get; set; }
public T Record { get; set; }
}

View file

@ -4,7 +4,7 @@ namespace LANCommander.Server.ImportExport.Models;
public class ImportQueueItem
{
public Guid Id { get; set; }
public string Key { get; set; }
public ImportExportRecordType Type { get; set; }
public object Record { get; set; }
public bool Processed { get; set; }

View file

@ -0,0 +1,9 @@
namespace LANCommander.Server.ImportExport.Models;
public class ImportStatusUpdate
{
public int Index { get; set; }
public int Total { get; set; }
public string? Error { get; set; }
public IImportItemInfo? CurrentItem { get; set; }
}

View file

@ -1,23 +1,46 @@
using LANCommander.Server.ImportExport.Importers;
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.ImportExport.Services;
public class ImportService : IDisposable
{
private Dictionary<Guid, ImportContext> ImportContexts = new();
public EventHandler<ImportStatusUpdate> OnImportStarted;
public EventHandler<ImportStatusUpdate> OnImportComplete;
public EventHandler<ImportStatusUpdate> OnImportStatusUpdate;
public EventHandler<ImportStatusUpdate> OnImportError;
private Dictionary<Guid, ImportContext> _importContexts = new();
public Guid EnqueueContext(ImportContext context)
public Guid AddContext(ImportContext context)
{
var id = Guid.NewGuid();
ImportContexts.Add(id, context);
context.SetId(id);
_importContexts.Add(id, context);
context.OnImportStarted += OnImportStarted;
context.OnImportComplete += OnImportComplete;
context.OnImportStatusUpdate += OnImportStatusUpdate;
context.OnImportError += OnImportError;
return id;
}
public void RemoveContext(Guid id)
{
_importContexts.Remove(id);
}
public IEnumerable<ImportContext> GetContexts()
{
return _importContexts.Values;
}
public ImportContext GetContext(Guid id)
{
if (ImportContexts.TryGetValue(id, out var context))
if (_importContexts.TryGetValue(id, out var context))
return context;
return null;

View file

@ -5,6 +5,7 @@ using LANCommander.Server.Services.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Linq.Expressions;
using System.Reflection;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using LANCommander.Server.Services.Abstractions;
@ -378,6 +379,88 @@ namespace LANCommander.Server.Services
Reset();
}
}
public virtual async Task SyncRelatedCollectionAsync<T, TChild, U>(
T entity,
Expression<Func<T, ICollection<TChild>>> navigationProperty,
IEnumerable<U> records,
Func<U, Expression<Func<TChild, bool>>> matchExpression,
Action<TChild, U> updateAction) where TChild : class where T : class
{
using var context = await dbContextFactory.CreateDbContextAsync();
var entry = context.Entry(entity);
var enumerableExpr = Expression.Lambda<Func<T, IEnumerable<TChild>>>(
navigationProperty.Body,
navigationProperty.Parameters);
var collectionEntry = entry.Collection(enumerableExpr);
if (!collectionEntry.IsLoaded)
await collectionEntry.LoadAsync();
var collection = navigationProperty.Compile().Invoke(entity);
if (collection == null)
{
collection = new List<TChild>();
if (navigationProperty.Body is not MemberExpression memberExpression ||
memberExpression.Member is not PropertyInfo propertyInfo)
throw new InvalidOperationException($"Navigation expression '{navigationProperty}' must point to a property.");
propertyInfo.SetValue(entity, collection);
}
var matchedChildren = new HashSet<TChild>();
foreach (var record in records)
{
var matchPredicate = matchExpression(record);
var existingChild = collection.FirstOrDefault(matchPredicate.Compile());
if (existingChild == null)
{
existingChild = await context.Set<TChild>()
.FirstOrDefaultAsync(matchPredicate);
}
if (existingChild != null)
{
if (!collection.Contains(existingChild))
collection.Add(existingChild);
updateAction(existingChild, record);
matchedChildren.Add(existingChild);
}
else
{
var newChild = Activator.CreateInstance<TChild>();
updateAction(newChild, record);
collection.Add(newChild);
matchedChildren.Add(newChild);
}
}
var toDelete = collection.Where(child => !matchedChildren.Contains(child));
foreach (var child in toDelete)
{
collection.Remove(child);
context.Remove(child);
}
try
{
await context.SaveChangesAsync();
}
finally
{
Reset();
}
}
private async Task<User?> GetCurrentUserAsync(DatabaseContext context)
{