Separate import and export functionality

This refactor splits the import and export functionality out of the ImportContext and adds an ExportContext. This also introduced separate importer and exporter implementations.

Additionally, adding archive, save, scripts, and server files to the final export archive has been implemented at the exporter implementation for each instead of handling it in the context. This should be a good pattern if other files need to be added down the road.
This commit is contained in:
Pat Hartl 2025-07-27 17:51:50 -05:00
parent 1250315110
commit 6ec919d1be
76 changed files with 2093 additions and 1573 deletions

View file

@ -5,6 +5,7 @@ namespace LANCommander.SDK.Enums;
[Flags]
public enum ExportRecordFlags
{
None = 0,
Actions = 1 << 0,
Archives = 1 << 1,
Collections = 1 << 2,

View file

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

View file

@ -1,4 +1,4 @@
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Exceptions;
public class ImportSkippedException<TRecord> : Exception where TRecord : class
{

View file

@ -0,0 +1,524 @@
using System.IO.Compression;
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Helpers;
using LANCommander.Server.Data.Models;
using LANCommander.Server.ImportExport.Exporters;
using LANCommander.Server.ImportExport.Importers;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ZipArchive = System.IO.Compression.ZipArchive;
namespace LANCommander.Server.ImportExport;
public class ExportContext(
BaseExporter<SDK.Models.Manifest.Game, Data.Models.Game> gameExporter,
BaseExporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> redistributableExporter,
BaseExporter<SDK.Models.Manifest.Server, Data.Models.Server> serverExporter,
GameService gameService,
RedistributableService redistributableService,
ServerService serverService,
ArchiveService archiveService,
MediaService mediaService,
GameSaveService saveService,
BaseExporter<SDK.Models.Manifest.Action, Data.Models.Action> actionExporter,
BaseExporter<SDK.Models.Manifest.Archive, Data.Models.Archive> archiveExporter,
BaseExporter<SDK.Models.Manifest.Collection, Data.Models.Collection> collectionExporter,
BaseExporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> customFieldExporter,
DeveloperExporter developerExporter,
PublisherExporter publisherExporter,
BaseExporter<SDK.Models.Manifest.Engine, Data.Models.Engine> engineExporter,
BaseExporter<SDK.Models.Manifest.Genre, Data.Models.Genre> genreExporter,
BaseExporter<SDK.Models.Manifest.Key, Data.Models.Key> keyExporter,
BaseExporter<SDK.Models.Manifest.Media, Data.Models.Media> mediaExporter,
BaseExporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> multiplayerModeExporter,
BaseExporter<SDK.Models.Manifest.Platform, Data.Models.Platform> platformExporter,
BaseExporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> playSessionExporter,
BaseExporter<SDK.Models.Manifest.Save, Data.Models.GameSave> saveExporter,
BaseExporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> savePathExporter,
BaseExporter<SDK.Models.Manifest.Script, Data.Models.Script> scriptExporter,
BaseExporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> serverConsoleExporter,
BaseExporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> serverHttpPathExporter,
BaseExporter<SDK.Models.Manifest.Tag, Data.Models.Tag> tagExporter,
IMapper mapper,
ILogger<ExportContext> logger) : IDisposable
{
public object Manifest { get; private set; }
public BaseModel DataRecord { get; private set; }
public StorageLocation ArchiveStorageLocation { get; }
public ZipArchive Archive { get; private set; }
public BaseExporter<SDK.Models.Manifest.Game, Data.Models.Game> Games { get; private set; } = gameExporter;
public BaseExporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> Redistributables { get; private set; } = redistributableExporter;
public BaseExporter<SDK.Models.Manifest.Server, Data.Models.Server> Servers { get; private set; } = serverExporter;
public BaseExporter<SDK.Models.Manifest.Action, Data.Models.Action> Actions = actionExporter;
public BaseExporter<SDK.Models.Manifest.Archive, Data.Models.Archive> Archives = archiveExporter;
public BaseExporter<SDK.Models.Manifest.Collection, Data.Models.Collection> Collections = collectionExporter;
public BaseExporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> CustomFields = customFieldExporter;
public BaseExporter<SDK.Models.Manifest.Company, Data.Models.Company> Developers = developerExporter;
public BaseExporter<SDK.Models.Manifest.Engine, Data.Models.Engine> Engines = engineExporter;
public BaseExporter<SDK.Models.Manifest.Genre, Data.Models.Genre> Genres = genreExporter;
public BaseExporter<SDK.Models.Manifest.Key, Data.Models.Key> Keys = keyExporter;
public BaseExporter<SDK.Models.Manifest.Media, Data.Models.Media> Media = mediaExporter;
public BaseExporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> MultiplayerModes = multiplayerModeExporter;
public BaseExporter<SDK.Models.Manifest.Platform, Data.Models.Platform> Platforms = platformExporter;
public BaseExporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> PlaySessions = playSessionExporter;
public BaseExporter<SDK.Models.Manifest.Company, Data.Models.Company> Publishers = publisherExporter;
public BaseExporter<SDK.Models.Manifest.Save, Data.Models.GameSave> Saves = saveExporter;
public BaseExporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> SavePaths = savePathExporter;
public BaseExporter<SDK.Models.Manifest.Script, Data.Models.Script> Scripts = scriptExporter;
public BaseExporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> ServerConsoles = serverConsoleExporter;
public BaseExporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> ServerHttpPaths = serverHttpPathExporter;
public BaseExporter<SDK.Models.Manifest.Tag, Data.Models.Tag> Tags = tagExporter;
public int Remaining => _queue.Count;
public int Processed => _queue.Count(qi => qi.Processed);
public int Total => _queue.Count;
private List<ExportQueueItem> _queue { get; } = new();
public Dictionary<ExportQueueItem, string> Errored { get; } = new();
public EventHandler<ExportQueueItem> OnRecordAdded;
public EventHandler<ExportQueueItem> OnRecordProcessed;
public EventHandler<ExportQueueItem> OnRecordError;
private void UseContext(ExportContext 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);
}
#region Initialize Export
public async Task<IEnumerable<ExportItemInfo>> InitializeExportAsync(Guid recordId, ExportRecordType recordType)
{
UseContext(this);
switch (recordType)
{
case ExportRecordType.Game:
return await InitializeGameExportAsync(recordId);
case ExportRecordType.Redistributable:
return await InitializeRedistributableExportAsync(recordId);
case ExportRecordType.Server:
return await InitializeServerExportAsync(recordId);
default:
throw new InvalidOperationException("Unknown record type");
}
}
private async Task<IEnumerable<ExportItemInfo>> InitializeGameExportAsync(Guid gameId)
{
var game = await gameService.Query(q =>
{
return q
.AsNoTracking()
.AsSplitQuery()
.Include(g => g.Actions)
.Include(g => g.Archives)
.Include(g => g.BaseGame)
.Include(g => g.Categories)
.Include(g => g.Collections)
.Include(g => g.CustomFields)
.Include(g => g.DependentGames)
.Include(g => g.Developers)
.Include(g => g.Engine)
.Include(g => g.Genres)
.Include(g => g.Media)
.Include(g => g.MultiplayerModes)
.Include(g => g.Platforms)
.Include(g => g.Publishers)
.Include(g => g.Redistributables)
.Include(g => g.SavePaths)
.Include(g => g.Tags);
}).GetAsync(gameId);
var gameManifest = mapper.Map<SDK.Models.Manifest.Game>(game);
DataRecord = game;
Manifest = gameManifest;
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Actions, Actions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Archives, Archives).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Collections, Collections).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.CustomFields, CustomFields).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Developers, Developers).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync([game.Engine], Engines).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Genres, Genres).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Keys, Keys).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Media, Media).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.MultiplayerModes, MultiplayerModes).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Platforms, Platforms).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.PlaySessions, PlaySessions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Publishers, Publishers).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.GameSaves, Saves).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.SavePaths, SavePaths).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Scripts, Scripts).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Tags, Tags).ToListAsync());
return exportItemInfo;
}
private async Task<IEnumerable<ExportItemInfo>> InitializeRedistributableExportAsync(Guid redistributableId)
{
var redistributable = await redistributableService
.AsNoTracking()
.AsSplitQuery()
.Query(q =>
{
return q
.Include(r => r.Archives)
.Include(r => r.Scripts);
})
.GetAsync(redistributableId);
Manifest = mapper.Map<SDK.Models.Manifest.Redistributable>(redistributable);
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(redistributable.Archives, Archives).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(redistributable.Scripts, Scripts).ToListAsync());
return exportItemInfo;
}
private async Task<IEnumerable<ExportItemInfo>> InitializeServerExportAsync(Guid serverId)
{
var server = await serverService
.AsNoTracking()
.AsSplitQuery()
.Query(q =>
{
return q
.Include(s => s.Actions)
.Include(s => s.HttpPaths)
.Include(s => s.ServerConsoles)
.Include(s => s.Scripts);
})
.GetAsync(serverId);
Manifest = mapper.Map<SDK.Models.Manifest.Server>(server);
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.Actions, Actions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.Scripts, Scripts).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.ServerConsoles, ServerConsoles).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.HttpPaths, ServerHttpPaths).ToListAsync());
return exportItemInfo;
}
#endregion
#region Prepare Export Queue
public async Task PrepareExportQueueAsync(ExportRecordFlags importRecordFlags)
{
if (DataRecord is Data.Models.Game game)
await PrepareGameExportQueueAsync(game, importRecordFlags);
if (DataRecord is Data.Models.Redistributable redistributable)
await PrepareRedistributableExportQueueAsync(redistributable, importRecordFlags);
if (DataRecord is Data.Models.Server server)
await PrepareServerExportQueueAsync(server, importRecordFlags);
}
public async Task PrepareGameExportQueueAsync(Game game, ExportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ExportRecordFlags.Actions))
await AddToExportQueueAsync(ExportRecordFlags.Actions, game.Actions);
if (importRecordFlags.HasFlag(ExportRecordFlags.Archives))
await AddToExportQueueAsync(ExportRecordFlags.Archives, game.Archives);
if (importRecordFlags.HasFlag(ExportRecordFlags.Collections))
await AddToExportQueueAsync(ExportRecordFlags.Collections, game.Collections);
if (importRecordFlags.HasFlag(ExportRecordFlags.CustomFields))
await AddToExportQueueAsync(ExportRecordFlags.CustomFields, game.CustomFields);
if (importRecordFlags.HasFlag(ExportRecordFlags.Developers))
await AddToExportQueueAsync(ExportRecordFlags.Developers, game.Developers);
if (importRecordFlags.HasFlag(ExportRecordFlags.Engine) && game.Engine != null)
await AddToExportQueueAsync(ExportRecordFlags.Engine, [game.Engine]);
if (importRecordFlags.HasFlag(ExportRecordFlags.Genres))
await AddToExportQueueAsync(ExportRecordFlags.Genres, game.Genres);
if (importRecordFlags.HasFlag(ExportRecordFlags.Keys))
await AddToExportQueueAsync(ExportRecordFlags.Keys, game.Keys);
if (importRecordFlags.HasFlag(ExportRecordFlags.Media))
await AddToExportQueueAsync(ExportRecordFlags.Media, game.Media);
if (importRecordFlags.HasFlag(ExportRecordFlags.MultiplayerModes))
await AddToExportQueueAsync(ExportRecordFlags.MultiplayerModes, game.MultiplayerModes);
if (importRecordFlags.HasFlag(ExportRecordFlags.Platforms))
await AddToExportQueueAsync(ExportRecordFlags.Platforms, game.Platforms);
if (importRecordFlags.HasFlag(ExportRecordFlags.PlaySessions))
await AddToExportQueueAsync(ExportRecordFlags.PlaySessions, game.PlaySessions);
if (importRecordFlags.HasFlag(ExportRecordFlags.Publishers))
await AddToExportQueueAsync(ExportRecordFlags.Publishers, game.Publishers);
if (importRecordFlags.HasFlag(ExportRecordFlags.Saves))
await AddToExportQueueAsync(ExportRecordFlags.Saves, game.GameSaves);
if (importRecordFlags.HasFlag(ExportRecordFlags.SavePaths))
await AddToExportQueueAsync(ExportRecordFlags.SavePaths, game.SavePaths);
if (importRecordFlags.HasFlag(ExportRecordFlags.Scripts))
await AddToExportQueueAsync(ExportRecordFlags.Scripts, game.Scripts);
if (importRecordFlags.HasFlag(ExportRecordFlags.Tags))
await AddToExportQueueAsync(ExportRecordFlags.Tags, game.Tags);
}
public async Task PrepareRedistributableExportQueueAsync(Data.Models.Redistributable redistributable,
ExportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ExportRecordFlags.Archives))
await AddToExportQueueAsync(ExportRecordFlags.Archives, redistributable.Archives);
if (importRecordFlags.HasFlag(ExportRecordFlags.Scripts))
await AddToExportQueueAsync(ExportRecordFlags.Scripts, redistributable.Scripts);
}
public async Task PrepareServerExportQueueAsync(Data.Models.Server server,
ExportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ExportRecordFlags.Actions))
await AddToExportQueueAsync(ExportRecordFlags.Actions, server.Actions);
if (importRecordFlags.HasFlag(ExportRecordFlags.Scripts))
await AddToExportQueueAsync(ExportRecordFlags.Scripts, server.Scripts);
if (importRecordFlags.HasFlag(ExportRecordFlags.ServerConsoles))
await AddToExportQueueAsync(ExportRecordFlags.ServerConsoles, server.ServerConsoles);
if (importRecordFlags.HasFlag(ExportRecordFlags.ServerHttpPaths))
await AddToExportQueueAsync(ExportRecordFlags.ServerHttpPaths, server.HttpPaths);
}
#endregion
private async Task AddToExportQueueAsync<TEntity>(ExportRecordFlags type, IEnumerable<TEntity> records) where TEntity : Data.Models.BaseModel
{
if (records != null)
_queue.AddRange(records.Select(r => new ExportQueueItem(r.Id, type, r)));
}
/// <summary>
/// Process the context's data record and write the archive to the supplied stream
/// </summary>
/// <param name="stream">Output stream for the resulting archive to be written to</param>
public async Task ExportQueueAsync(Stream stream)
{
SDK.Models.Manifest.Game gameManifest = null;
SDK.Models.Manifest.Redistributable redistributableManifest = null;
SDK.Models.Manifest.Server serverManifest = null;
if (DataRecord is Data.Models.Game game)
gameManifest = await ExportGameQueueAsync(game);
else if (DataRecord is Data.Models.Redistributable redistributable)
redistributableManifest = await ExportRedistributableQueueAsync(redistributable);
else if (DataRecord is Data.Models.Server server)
serverManifest = await ExportServerQueueAsync(server);
using (var export = new ZipArchive(stream, ZipArchiveMode.Create))
{
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms))
{
if (gameManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(gameManifest));
else if (redistributableManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(redistributableManifest));
else if (serverManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(serverManifest));
await writer.FlushAsync();
var manifestEntry = export.CreateEntry(ManifestHelper.ManifestFilename, CompressionLevel.NoCompression);
await using (var entryStream = manifestEntry.Open())
{
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(entryStream);
}
}
}
}
public async Task<SDK.Models.Manifest.Game> ExportGameQueueAsync(Data.Models.Game game)
{
var manifest = await Games.ExportAsync(game.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ExportRecordFlags.Actions)
manifest.Actions.Add(await ExportRecordAsync(queueItem, Actions));
else if (queueItem.Type == ExportRecordFlags.Archives)
manifest.Archives.Add(await ExportRecordAsync(queueItem, Archives));
else if (queueItem.Type == ExportRecordFlags.Collections)
manifest.Collections.Add(await ExportRecordAsync(queueItem, Collections));
else if (queueItem.Type == ExportRecordFlags.CustomFields)
manifest.CustomFields.Add(await ExportRecordAsync(queueItem, CustomFields));
else if (queueItem.Type == ExportRecordFlags.Developers)
manifest.Developers.Add(await ExportRecordAsync(queueItem, Developers));
else if (queueItem.Type == ExportRecordFlags.Publishers)
manifest.Publishers.Add(await ExportRecordAsync(queueItem, Publishers));
else if (queueItem.Type == ExportRecordFlags.Engine)
manifest.Engine = await ExportRecordAsync(queueItem, Engines);
else if (queueItem.Type == ExportRecordFlags.Genres)
manifest.Genres.Add(await ExportRecordAsync(queueItem, Genres));
else if (queueItem.Type == ExportRecordFlags.Keys)
manifest.Keys.Add(await ExportRecordAsync(queueItem, Keys));
else if (queueItem.Type == ExportRecordFlags.Media)
manifest.Media.Add(await ExportRecordAsync(queueItem, Media));
else if (queueItem.Type == ExportRecordFlags.MultiplayerModes)
manifest.MultiplayerModes.Add(await ExportRecordAsync(queueItem, MultiplayerModes));
else if (queueItem.Type == ExportRecordFlags.Platforms)
manifest.Platforms.Add(await ExportRecordAsync(queueItem, Platforms));
else if (queueItem.Type == ExportRecordFlags.PlaySessions)
manifest.PlaySessions.Add(await ExportRecordAsync(queueItem, PlaySessions));
else if (queueItem.Type == ExportRecordFlags.Saves)
manifest.Saves.Add(await ExportRecordAsync(queueItem, Saves));
else if (queueItem.Type == ExportRecordFlags.SavePaths)
manifest.SavePaths.Add(await ExportRecordAsync(queueItem, SavePaths));
else if (queueItem.Type == ExportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
else if (queueItem.Type == ExportRecordFlags.Tags)
manifest.Tags.Add(await ExportRecordAsync(queueItem, Tags));
}
return manifest;
}
public async Task<SDK.Models.Manifest.Redistributable> ExportRedistributableQueueAsync(Data.Models.Redistributable redistributable)
{
var manifest = await Redistributables.ExportAsync(redistributable.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ExportRecordFlags.Archives)
manifest.Archives.Add(await ExportRecordAsync(queueItem, Archives));
else if (queueItem.Type == ExportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
}
return manifest;
}
public async Task<SDK.Models.Manifest.Server> ExportServerQueueAsync(Data.Models.Server server)
{
var manifest = await Servers.ExportAsync(server.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ExportRecordFlags.Actions)
manifest.Actions.Add(await ExportRecordAsync(queueItem, Actions));
else if (queueItem.Type == ExportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
else if (queueItem.Type == ExportRecordFlags.ServerConsoles)
manifest.ServerConsoles.Add(await ExportRecordAsync(queueItem, ServerConsoles));
else if (queueItem.Type == ExportRecordFlags.ServerHttpPaths)
await ExportRecordAsync(queueItem, ServerHttpPaths);
}
return manifest;
}
private async Task AddSaveToExport(SDK.Models.Manifest.Save save, System.IO.Compression.ZipArchive zip)
{
try
{
var savePath = await saveService.GetSavePathAsync(save.Id);
if (Path.Exists(savePath))
{
var saveEntry = zip.CreateEntry($"Saves/{save.Id}");
using (var saveEntryStream = saveEntry.Open())
using (var saveFileStream = new FileStream(savePath, FileMode.Open))
{
await saveFileStream.CopyToAsync(saveEntryStream);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Could not add save {save.Id} to export file");
}
}
private async IAsyncEnumerable<ExportItemInfo> GetExportItemInfoAsync<TModel, TEntity>(IEnumerable<TEntity> records,
BaseExporter<TModel, TEntity> exporter)
{
if (records != null)
foreach (var record in records)
{
if (record != null && record.GetType() == typeof(TEntity))
yield return await exporter.GetExportInfoAsync(record);
}
}
private async Task<TRecord> ExportRecordAsync<TRecord, TEntity>(ExportQueueItem queueItem, BaseExporter<TRecord, TEntity> exporter) where TEntity : BaseModel
{
var entity = queueItem.Record as TEntity;
try
{
var record = await exporter.ExportAsync(entity.Id);
queueItem.Processed = true;
OnRecordProcessed?.Invoke(this, queueItem);
return record;
}
catch (Exception ex)
{
Errored.Add(queueItem, ex.Message);
OnRecordError?.Invoke(this, queueItem);
return default;
}
}
public void Dispose()
{
if (Archive != null)
Archive.Dispose();
}
}

View file

@ -0,0 +1,20 @@
using LANCommander.SDK.Enums;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using Action = LANCommander.SDK.Models.Manifest.Action;
namespace LANCommander.Server.ImportExport.Exporters;
public class ActionExporter(
ActionService actionService) : BaseExporter<Action, Data.Models.Action>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Action record) =>
await Task.Run(() => new ExportItemInfo { Id = record.Id, Name = record.Name, Flag = ExportRecordFlags.Actions });
public override bool CanExport(Action record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Action> ExportAsync(Guid id)
{
return await actionService.GetAsync<Action>(id);
}
}

View file

@ -0,0 +1,70 @@
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;
namespace LANCommander.Server.ImportExport.Exporters;
/// <summary>
/// Implements exporting for archive records
/// </summary>
/// <param name="mapper">AutoMapper instance for mapping between models</param>
/// <param name="archiveService">Service for archive operations</param>
public class ArchiveExporter(
IMapper mapper,
ArchiveService archiveService) : BaseExporter<Archive, Data.Models.Archive>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Archive record)
{
var archivePath = await archiveService.GetArchiveFileLocationAsync(record.ObjectKey);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Archives,
Name = record.Version,
};
if (File.Exists(archivePath))
{
var fileInfo = new FileInfo(archivePath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanExport(Archive record) => ExportContext.DataRecord is Data.Models.Game || ExportContext.DataRecord is Data.Models.Redistributable;
public override async Task<Archive> ExportAsync(Guid id)
{
var entity = await archiveService.GetAsync(id);
try
{
var path = await archiveService.GetArchiveFileLocationAsync(entity);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
var archiveEntry = ExportContext.Archive.CreateEntry($"Archives/{entity.Id}");
using (var archiveEntryStream = archiveEntry.Open())
using (var archiveFileStream = new FileStream(path, FileMode.Open))
{
await archiveFileStream.CopyToAsync(archiveEntryStream);
}
}
return mapper.Map<Archive>(entity);
}
catch (Exception ex)
{
throw new ExportSkippedException<Data.Models.Archive>(entity, "Could not add archive to export file", ex);
}
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class CollectionExporter(
CollectionService collectionService) : BaseExporter<Collection, Data.Models.Collection>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Collection record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Collections,
Name = record.Name,
};
}
public override bool CanExport(Collection record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Collection> ExportAsync(Guid id)
{
return await collectionService.GetAsync<Collection>(id);
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class CustomFieldExporter(
GameCustomFieldService gameCustomFieldService) : BaseExporter<GameCustomField, Data.Models.GameCustomField>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.GameCustomField record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.CustomFields,
Name = record.Name,
};
}
public override bool CanExport(GameCustomField record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<GameCustomField> ExportAsync(Guid id)
{
return await gameCustomFieldService.GetAsync<GameCustomField>(id);
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class DeveloperExporter(
CompanyService companyService) : BaseExporter<Company, Data.Models.Company>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Company record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Developers,
Name = record.Name,
};
}
public override bool CanExport(Company record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Company> ExportAsync(Guid id)
{
return await companyService.GetAsync<Company>(id);
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class EngineExporter(
EngineService engineService) : BaseExporter<Engine, Data.Models.Engine>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Engine record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Engine,
Name = record.Name,
};
}
public override bool CanExport(Engine record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Engine> ExportAsync(Guid id)
{
return await engineService.GetAsync<Engine>(id);
}
}

View file

@ -0,0 +1,46 @@
using AutoMapper;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class GameExporter(
GameService gameService) : BaseExporter<Game, Data.Models.Game>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Game record)
{
return new ExportItemInfo
{
Id = record.Id,
Name = record.Title,
};
}
public override bool CanExport(Game record) => true;
public override async Task<Game> ExportAsync(Guid id)
{
// Retrieve the game again, but only get the minimal amount of data required
var manifest = await gameService.GetAsync<Game>(id);
manifest.Actions ??= new List<SDK.Models.Manifest.Action>();
manifest.Archives ??= new List<SDK.Models.Manifest.Archive>();
manifest.Collections ??= new List<SDK.Models.Manifest.Collection>();
manifest.CustomFields ??= new List<SDK.Models.Manifest.GameCustomField>();
manifest.Developers ??= new List<SDK.Models.Manifest.Company>();
manifest.Genres ??= new List<SDK.Models.Manifest.Genre>();
manifest.Keys ??= new List<SDK.Models.Manifest.Key>();
manifest.Media ??= new List<SDK.Models.Manifest.Media>();
manifest.MultiplayerModes ??= new List<SDK.Models.Manifest.MultiplayerMode>();
manifest.Platforms ??= new List<SDK.Models.Manifest.Platform>();
manifest.PlaySessions ??= new List<SDK.Models.Manifest.PlaySession>();
manifest.Publishers ??= new List<SDK.Models.Manifest.Company>();
manifest.Saves ??= new List<SDK.Models.Manifest.Save>();
manifest.SavePaths ??= new List<SDK.Models.Manifest.SavePath>();
manifest.Scripts ??= new List<SDK.Models.Manifest.Script>();
manifest.Tags ??= new List<SDK.Models.Manifest.Tag>();
return manifest;
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class GenreExporter(
GenreService genreService) : BaseExporter<Genre, Data.Models.Genre>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Genre record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Genres,
Name = record.Name,
};
}
public override bool CanExport(Genre record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Genre> ExportAsync(Guid id)
{
return await genreService.GetAsync<Genre>(id);
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class KeyExporter(
KeyService keyService) : BaseExporter<Key, Data.Models.Key>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Key record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Keys,
Name = new String('*', record.Value.Length),
};
}
public override bool CanExport(Key record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Key> ExportAsync(Guid id)
{
return await keyService.GetAsync<Key>(id);
}
}

View file

@ -0,0 +1,65 @@
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;
namespace LANCommander.Server.ImportExport.Exporters;
public class MediaExporter(
MediaService mediaService) : BaseExporter<Media, Data.Models.Media>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Media record)
{
var mediaPath = await mediaService.GetMediaPathAsync(record.Id);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Media,
Name = String.IsNullOrWhiteSpace(record.Name) ? record.Type.ToString() : $"{record.Type} - {record.Name}",
};
if (File.Exists(mediaPath))
{
var fileInfo = new FileInfo(mediaPath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanExport(Media record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Media> ExportAsync(Guid id)
{
var entity = await mediaService.GetAsync(id);
try
{
var path = await mediaService.GetMediaPathAsync(id);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
var mediaEntry = ExportContext.Archive.CreateEntry($"Media/{entity.Id}");
using (var mediaEntryStream = mediaEntry.Open())
using (var mediaFileStream = new FileStream(path, FileMode.Open))
{
await mediaFileStream.CopyToAsync(mediaEntryStream);
}
}
return await mediaService.GetAsync<Media>(id);
}
catch (Exception ex)
{
throw new ExportSkippedException<Data.Models.Media>(entity, "Could not add media to export file", ex);
}
}
}

View file

@ -0,0 +1,28 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class MultiplayerModeExporter(
MultiplayerModeService multiplayerModeService) : BaseExporter<MultiplayerMode, Data.Models.MultiplayerMode>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.MultiplayerMode record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.MultiplayerModes,
Name = String.IsNullOrWhiteSpace(record.Description) ? record.Type.ToString() : $"{record.Type} - {record.Description}",
};
}
public override bool CanExport(MultiplayerMode record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<MultiplayerMode> ExportAsync(Guid id)
{
return await multiplayerModeService.GetAsync<MultiplayerMode>(id);
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class PlatformExporter(PlatformService platformService) : BaseExporter<Platform, Data.Models.Platform>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Platform record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Platforms,
Name = record.Name,
};
}
public override bool CanExport(Platform record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Platform> ExportAsync(Guid id)
{
return await platformService.GetAsync<Platform>(id);
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class PlaySessionExporter(PlaySessionService playSessionService) : BaseExporter<PlaySession, Data.Models.PlaySession>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.PlaySession record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.PlaySessions,
Name = $"{record.User} - {record.Start}-{record.End}",
};
}
public override bool CanExport(PlaySession record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<PlaySession> ExportAsync(Guid id)
{
return await playSessionService.GetAsync<PlaySession>(id);
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class PublisherExporter(CompanyService companyService) : BaseExporter<Company, Data.Models.Company>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Company record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Publishers,
Name = record.Name,
};
}
public override bool CanExport(Company record) => ExportContext.DataRecord is Data.Models.Company;
public override async Task<Company> ExportAsync(Guid id)
{
return await companyService.GetAsync<Company>(id);
}
}

View file

@ -0,0 +1,25 @@
using AutoMapper;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class RedistributableExporter(RedistributableService redistributableService) : BaseExporter<Redistributable, Data.Models.Redistributable>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Redistributable record)
{
return new ExportItemInfo()
{
Id = record.Id,
Name = record.Name,
};
}
public override bool CanExport(Redistributable record) => true;
public override async Task<Redistributable> ExportAsync(Guid id)
{
return await redistributableService.GetAsync<Redistributable>(id);
}
}

View file

@ -0,0 +1,64 @@
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;
namespace LANCommander.Server.ImportExport.Exporters;
public class SaveExporter(
IMapper mapper,
GameSaveService gameSaveService) : BaseExporter<Save, Data.Models.GameSave>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.GameSave record)
{
var savePath = await gameSaveService.GetSavePathAsync(record.Id);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Saves,
Name = $"{record.User} - {record.CreatedOn}",
};
if (File.Exists(savePath))
{
var fileInfo = new FileInfo(savePath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanExport(Save record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Save> ExportAsync(Guid id)
{
var entity = await gameSaveService.GetAsync(id);
try
{
var savePath = await gameSaveService.GetSavePathAsync(id);
if (Path.Exists(savePath))
{
var saveEntry = ExportContext.Archive.CreateEntry($"Saves/{id}");
using (var saveEntryStream = saveEntry.Open())
using (var saveFileStream = new FileStream(savePath, FileMode.Open))
{
await saveFileStream.CopyToAsync(saveEntryStream);
}
}
return mapper.Map<Save>(entity);
}
catch (Exception ex)
{
throw new ExportSkippedException<Data.Models.GameSave>(entity, "Could not add save to export file", ex);
}
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class SavePathExporter(SavePathService savePathService) : BaseExporter<SavePath, Data.Models.SavePath>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.SavePath record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.SavePaths,
Name = record.Path,
};
}
public override bool CanExport(SavePath record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<SavePath> ExportAsync(Guid id)
{
return await savePathService.GetAsync<SavePath>(id);
}
}

View file

@ -0,0 +1,60 @@
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;
namespace LANCommander.Server.ImportExport.Exporters;
public class ScriptExporter(
IMapper mapper,
ScriptService scriptService) : BaseExporter<Script, Data.Models.Script>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Script record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Scripts,
Name = record.Name,
Size = record.Contents.Length,
};
}
public override bool CanExport(Script record) =>
ExportContext.DataRecord is Data.Models.Game
||
ExportContext.DataRecord is Data.Models.Redistributable
||
ExportContext.DataRecord is Data.Models.Server;
public override async Task<Script> ExportAsync(Guid id)
{
var entity = await scriptService.GetAsync(id);
try
{
var scriptEntry = ExportContext.Archive.CreateEntry($"Scripts/{id}");
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms))
{
await writer.WriteAsync(entity.Contents);
await writer.FlushAsync();
await using (var entryStream = scriptEntry.Open())
{
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(entryStream);
}
}
return mapper.Map<Script>(entity);
}
catch (Exception ex)
{
throw new ExportSkippedException<Data.Models.Script>(entity, "Could not add script to export file", ex);
}
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class ServerConsoleExporter(ServerConsoleService serverConsoleService) : BaseExporter<ServerConsole, Data.Models.ServerConsole>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.ServerConsole record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.ServerConsoles,
Name = record.Name,
};
}
public override bool CanExport(ServerConsole record) => ExportContext.DataRecord is Data.Models.ServerConsole;
public override async Task<ServerConsole> ExportAsync(Guid id)
{
return await serverConsoleService.GetAsync<ServerConsole>(id);
}
}

View file

@ -0,0 +1,65 @@
using AutoMapper;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class ServerExporter(ServerService serverService) : BaseExporter<SDK.Models.Manifest.Server, Data.Models.Server>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Server record)
{
var files = Directory.GetFiles(record.WorkingDirectory, "*", SearchOption.AllDirectories);
long size = 0;
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
size += fileInfo.Length;
}
return new ExportItemInfo
{
Id = record.Id,
Name = record.Name,
Size = size
};
}
public override bool CanExport(SDK.Models.Manifest.Server record) => true;
public override async Task<SDK.Models.Manifest.Server> ExportAsync(Guid id)
{
var entity = await serverService.GetAsync<SDK.Models.Manifest.Server>(id);
var files = Directory.GetFiles(entity.WorkingDirectory, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
try
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
{
var fileEntry =
ExportContext.Archive.CreateEntry(
$"Files/{fileInfo.Name.Replace(Path.DirectorySeparatorChar, '/')}");
using (var fileEntryStream = fileEntry.Open())
using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open))
{
await fileStream.CopyToAsync(fileEntryStream);
}
}
}
catch (Exception ex)
{
// File could not be added to archive
}
}
return entity;
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class ServerHttpPathExporter(ServerHttpPathService serverHttpPathService) : BaseExporter<ServerHttpPath, Data.Models.ServerHttpPath>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.ServerHttpPath record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.ServerHttpPaths,
Name = record.Path,
};
}
public override bool CanExport(ServerHttpPath record) => ExportContext.DataRecord is Data.Models.Server;
public override async Task<ServerHttpPath> ExportAsync(Guid id)
{
return await serverHttpPathService.GetAsync<ServerHttpPath>(id);
}
}

View file

@ -0,0 +1,27 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Exporters;
public class TagExporter(TagService tagService) : BaseExporter<Tag, Data.Models.Tag>
{
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Tag record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ExportRecordFlags.Tags,
Name = record.Name,
};
}
public override bool CanExport(Tag record) => ExportContext.DataRecord is Data.Models.Game;
public override async Task<Tag> ExportAsync(Guid id)
{
return await tagService.GetAsync<Tag>(id);
}
}

View file

@ -0,0 +1,17 @@
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.ImportExport.Exporters;
public abstract class BaseExporter<TRecord, TEntity> : IExporter<TRecord, TEntity>
{
protected ExportContext ExportContext { get; private set; }
public void UseContext(ExportContext context)
{
ExportContext = context;
}
public abstract Task<ExportItemInfo> GetExportInfoAsync(TEntity record);
public abstract bool CanExport(TRecord record);
public abstract Task<TRecord> ExportAsync(Guid id);
}

View file

@ -0,0 +1,17 @@
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.ImportExport.Exporters;
/// <summary>
/// Implementations should be able to process a single record. This will be called
/// by the import service to process records in its queue. Only one instance of
/// this needs to exist for each type.
/// </summary>
/// <typeparam name="TRecord"></typeparam>
public interface IExporter<TRecord, TEntity>
{
void UseContext(ExportContext context);
Task<ExportItemInfo> GetExportInfoAsync(TEntity record);
bool CanExport(TRecord record);
Task<TRecord> ExportAsync(Guid id);
}

View file

@ -0,0 +1,51 @@
using LANCommander.Server.ImportExport.Factories;
using LANCommander.Server.ImportExport.Importers;
using LANCommander.Server.ImportExport.Services;
using LANCommander.Server.Services;
using Microsoft.Extensions.DependencyInjection;
using Settings = LANCommander.Server.Services.Models.Settings;
namespace LANCommander.Server.ImportExport.Extensions;
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddLANCommanderImportExport(this IServiceCollection services)
{
services.AddSingleton<ImportService>();
services.AddSingleton<ExportService>();
// Register importers
services.AddScoped<ImportContextFactory>();
services.AddScoped<ImportContext>();
services.AddScoped<ExportContextFactory>();
services.AddScoped<ExportContext>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game>, GameImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable>, RedistributableImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server>, ServerImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action>, ActionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive>, ArchiveImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection>, CollectionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField>, CustomFieldImporter>();
services.AddScoped<DeveloperImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection>, CollectionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine>, EngineImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre>, GenreImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key>, KeyImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media>, MediaImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode>, MultiplayerModeImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform>, PlatformImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession>, PlaySessionImporter>();
services.AddScoped<PublisherImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave>, SaveImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath>, SavePathImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script>, ScriptImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole>, ServerConsoleImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath>, ServerHttpPathImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag>, TagImporter>();
return services;
}
}

View file

@ -0,0 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
namespace LANCommander.Server.ImportExport.Factories;
public class ExportContextFactory(IServiceProvider serviceProvider)
{
public ExportContext Create()
{
var scope = serviceProvider.CreateScope();
return scope.ServiceProvider.GetService<ExportContext>();
}
}

View file

@ -1,7 +1,6 @@
using LANCommander.Server.Services.Importers;
using Microsoft.Extensions.DependencyInjection;
namespace LANCommander.Server.Services.Factories;
namespace LANCommander.Server.ImportExport.Factories;
public class ImportContextFactory(IServiceProvider serviceProvider)
{

View file

@ -0,0 +1,395 @@
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.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
namespace LANCommander.Server.ImportExport;
public class ImportContext(
BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game> gameImporter,
BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> redistributableImporter,
BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server> serverImporter,
GameService gameService,
RedistributableService redistributableService,
ServerService serverService,
ArchiveService archiveService,
MediaService mediaService,
GameSaveService saveService,
BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action> actionImporter,
BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive> archiveImporter,
BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection> collectionImporter,
BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> customFieldImporter,
DeveloperImporter developerImporter,
PublisherImporter publisherImporter,
BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine> engineImporter,
BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre> genreImporter,
BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key> keyImporter,
BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media> mediaImporter,
BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> multiplayerModeImporter,
BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform> platformImporter,
BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> playSessionImporter,
BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave> saveImporter,
BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> savePathImporter,
BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script> scriptImporter,
BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> serverConsoleImporter,
BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> serverHttpPathImporter,
BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag> tagImporter,
IMapper mapper,
ILogger<ImportContext> logger) : IDisposable
{
public object Manifest { get; private set; }
public BaseModel DataRecord { get; private set; }
public StorageLocation ArchiveStorageLocation { get; }
public ZipArchive Archive { get; private set; }
public BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game> Games { get; private set; } = gameImporter;
public BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> Redistributables { get; private set; } = redistributableImporter;
public BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server> Servers { get; private set; } = serverImporter;
public BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action> Actions = actionImporter;
public BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive> Archives = archiveImporter;
public BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection> Collections = collectionImporter;
public BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> CustomFields = customFieldImporter;
public BaseImporter<SDK.Models.Manifest.Company, Data.Models.Company> Developers = developerImporter;
public BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine> Engines = engineImporter;
public BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre> Genres = genreImporter;
public BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key> Keys = keyImporter;
public BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media> Media = mediaImporter;
public BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> MultiplayerModes = multiplayerModeImporter;
public BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform> Platforms = platformImporter;
public BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> PlaySessions = playSessionImporter;
public BaseImporter<SDK.Models.Manifest.Company, Data.Models.Company> Publishers = publisherImporter;
public BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave> Saves = saveImporter;
public BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> SavePaths = savePathImporter;
public BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script> Scripts = scriptImporter;
public BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> ServerConsoles = serverConsoleImporter;
public BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> ServerHttpPaths = serverHttpPathImporter;
public BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag> Tags = tagImporter;
public int Remaining => _queue.Count;
public int Processed => _queue.Count(qi => qi.Processed);
public int Total => _queue.Count;
private List<ImportQueueItem> _queue { get; } = new();
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);
}
#region Initialize Import
public async Task<IEnumerable<ImportItemInfo>> InitializeImportAsync(string archivePath)
{
UseContext(this);
Archive = ZipArchive.Open(archivePath);
var manifestEntry = Archive.Entries.FirstOrDefault(e => e.Key == ManifestHelper.ManifestFilename);
using (var reader = new StreamReader(manifestEntry.OpenEntryStream()))
{
var manifestContents = await reader.ReadToEndAsync();
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Game>(manifestContents, out var gameManifest))
return await InitializeGameImportAsync(gameManifest);
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Redistributable>(manifestContents, out var redistributableManifest))
return await InitializeRedistributableImportAsync(redistributableManifest);
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Server>(manifestContents, out var serverManifest))
return await InitializeServerImportAsync(serverManifest);
throw new InvalidOperationException("Unknown manifest file");
}
}
private async Task<IEnumerable<ImportItemInfo>> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest)
{
Manifest = gameManifest;
var importItemInfo = new List<ImportItemInfo>();
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.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)
{
Manifest = redistributableManifest;
var importItemInfo = new List<ImportItemInfo>();
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)
{
Manifest = serverManifest;
var importItemInfo = new List<ImportItemInfo>();
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(ImportRecordFlags importRecordFlags)
{
if (Manifest is SDK.Models.Manifest.Game gameManifest)
await PrepareGameImportQueueAsync(gameManifest, importRecordFlags);
if (Manifest is SDK.Models.Manifest.Redistributable redistributableManifest)
await PrepareRedistributableImportQueueAsync(redistributableManifest, importRecordFlags);
if (Manifest is SDK.Models.Manifest.Server serverManifest)
await PrepareServerImportQueueAsync(serverManifest, importRecordFlags);
}
public async Task PrepareGameImportQueueAsync(SDK.Models.Manifest.Game gameManifest, ImportRecordFlags importRecordFlags)
{
if (!(await gameImporter.ExistsAsync(gameManifest)))
DataRecord = await gameImporter.AddAsync(gameManifest);
else
DataRecord = await gameImporter.UpdateAsync(gameManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToImportQueueAsync(ImportRecordFlags.Actions, gameManifest.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToImportQueueAsync(ImportRecordFlags.Archives, gameManifest.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Collections))
await AddToImportQueueAsync(ImportRecordFlags.Collections, gameManifest.Collections);
if (importRecordFlags.HasFlag(ImportRecordFlags.CustomFields))
await AddToImportQueueAsync(ImportRecordFlags.CustomFields, gameManifest.CustomFields);
if (importRecordFlags.HasFlag(ImportRecordFlags.Developers))
await AddToImportQueueAsync(ImportRecordFlags.Developers, gameManifest.Developers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Engine) && gameManifest.Engine != null)
await AddToImportQueueAsync(ImportRecordFlags.Engine, [gameManifest.Engine]);
if (importRecordFlags.HasFlag(ImportRecordFlags.Genres))
await AddToImportQueueAsync(ImportRecordFlags.Genres, gameManifest.Genres);
if (importRecordFlags.HasFlag(ImportRecordFlags.Keys))
await AddToImportQueueAsync(ImportRecordFlags.Keys, gameManifest.Keys);
if (importRecordFlags.HasFlag(ImportRecordFlags.Media))
await AddToImportQueueAsync(ImportRecordFlags.Media, gameManifest.Media);
if (importRecordFlags.HasFlag(ImportRecordFlags.MultiplayerModes))
await AddToImportQueueAsync(ImportRecordFlags.MultiplayerModes, gameManifest.MultiplayerModes);
if (importRecordFlags.HasFlag(ImportRecordFlags.Platforms))
await AddToImportQueueAsync(ImportRecordFlags.Platforms, gameManifest.Platforms);
if (importRecordFlags.HasFlag(ImportRecordFlags.PlaySessions))
await AddToImportQueueAsync(ImportRecordFlags.PlaySessions, gameManifest.PlaySessions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Publishers))
await AddToImportQueueAsync(ImportRecordFlags.Publishers, gameManifest.Publishers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Saves))
await AddToImportQueueAsync(ImportRecordFlags.Saves, gameManifest.Saves);
if (importRecordFlags.HasFlag(ImportRecordFlags.SavePaths))
await AddToImportQueueAsync(ImportRecordFlags.SavePaths, gameManifest.SavePaths);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, gameManifest.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.Tags))
await AddToImportQueueAsync(ImportRecordFlags.Tags, gameManifest.Tags);
}
public async Task PrepareRedistributableImportQueueAsync(SDK.Models.Manifest.Redistributable redistributableManifest,
ImportRecordFlags importRecordFlags)
{
if (!(await redistributableImporter.ExistsAsync(redistributableManifest)))
DataRecord = await redistributableImporter.AddAsync(redistributableManifest);
else
DataRecord = await redistributableImporter.UpdateAsync(redistributableManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToImportQueueAsync(ImportRecordFlags.Archives, redistributableManifest.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, redistributableManifest.Scripts);
}
public async Task PrepareServerImportQueueAsync(SDK.Models.Manifest.Server serverManifest,
ImportRecordFlags importRecordFlags)
{
if (!(await serverImporter.ExistsAsync(serverManifest)))
DataRecord = await serverImporter.AddAsync(serverManifest);
else
DataRecord = await serverImporter.UpdateAsync(serverManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToImportQueueAsync(ImportRecordFlags.Actions, serverManifest.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, serverManifest.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerConsoles))
await AddToImportQueueAsync(ImportRecordFlags.ServerConsoles, serverManifest.ServerConsoles);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerHttpPaths))
await AddToImportQueueAsync(ImportRecordFlags.ServerHttpPaths, serverManifest.HttpPaths);
}
#endregion
private async Task AddToImportQueueAsync<TRecord>(ImportRecordFlags type, IEnumerable<TRecord> records) where TRecord : SDK.Models.Manifest.BaseModel
{
if (records != null)
_queue.AddRange(records.Select(r => new ImportQueueItem(type, r)));
}
public async Task ImportQueueAsync()
{
foreach (var queueItem in _queue)
{
if (queueItem.Type == ImportRecordFlags.Actions)
await ImportRecordAsync(queueItem, Actions);
else if (queueItem.Type == ImportRecordFlags.Archives)
await ImportRecordAsync(queueItem, Archives);
else if (queueItem.Type == ImportRecordFlags.Collections)
await ImportRecordAsync(queueItem, Collections);
else if (queueItem.Type == ImportRecordFlags.CustomFields)
await ImportRecordAsync(queueItem, CustomFields);
else if (queueItem.Type == ImportRecordFlags.Developers)
await ImportRecordAsync(queueItem, Developers);
else if (queueItem.Type == ImportRecordFlags.Publishers)
await ImportRecordAsync(queueItem, Publishers);
else if (queueItem.Type == ImportRecordFlags.Engine)
await ImportRecordAsync(queueItem, Engines);
else if (queueItem.Type == ImportRecordFlags.Genres)
await ImportRecordAsync(queueItem, Genres);
else if (queueItem.Type == ImportRecordFlags.Keys)
await ImportRecordAsync(queueItem, Keys);
else if (queueItem.Type == ImportRecordFlags.Media)
await ImportRecordAsync(queueItem, Media);
else if (queueItem.Type == ImportRecordFlags.MultiplayerModes)
await ImportRecordAsync(queueItem, MultiplayerModes);
else if (queueItem.Type == ImportRecordFlags.Platforms)
await ImportRecordAsync(queueItem, Platforms);
else if (queueItem.Type == ImportRecordFlags.PlaySessions)
await ImportRecordAsync(queueItem, PlaySessions);
else if (queueItem.Type == ImportRecordFlags.Saves)
await ImportRecordAsync(queueItem, Saves);
else if (queueItem.Type == ImportRecordFlags.SavePaths)
await ImportRecordAsync(queueItem, SavePaths);
else if (queueItem.Type == ImportRecordFlags.Scripts)
await ImportRecordAsync(queueItem, Scripts);
else if (queueItem.Type == ImportRecordFlags.ServerConsoles)
await ImportRecordAsync(queueItem, ServerConsoles);
else if (queueItem.Type == ImportRecordFlags.ServerHttpPaths)
await ImportRecordAsync(queueItem, ServerHttpPaths);
else if (queueItem.Type == ImportRecordFlags.Tags)
await ImportRecordAsync(queueItem, Tags);
}
}
private async IAsyncEnumerable<ImportItemInfo> GetImportItemInfoAsync<TModel, TEntity>(IEnumerable<TModel> records,
BaseImporter<TModel, TEntity> importer)
{
if (records != null)
foreach (var record in records)
{
if (record != null && record.GetType() == typeof(TModel))
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)
Archive.Dispose();
}
}

View file

@ -1,8 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using Action = LANCommander.SDK.Models.Manifest.Action;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class ActionImporter(
IMapper mapper,
@ -11,12 +14,8 @@ public class ActionImporter(
public override async Task<ImportItemInfo> GetImportInfoAsync(Action record) =>
await Task.Run(() => new ImportItemInfo { Name = record.Name, Flag = ImportRecordFlags.Actions });
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Action record) =>
await Task.Run(() => new ExportItemInfo { Id = record.Id, Name = record.Name, Flag = ImportRecordFlags.Actions });
public override bool CanImport(Action record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Action record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Action> AddAsync(Action record)
{
@ -64,11 +63,6 @@ public class ActionImporter(
}
}
public override async Task<Action> ExportAsync(Guid id)
{
return await actionService.GetAsync<Action>(id);
}
public override async Task<bool> ExistsAsync(Action record)
{
return await actionService.ExistsAsync(a => a.Name == record.Name && a.GameId == ImportContext.DataRecord.Id);

View file

@ -1,11 +1,11 @@
using System.Web.Services.Description;
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using SharpCompress.Archives.Zip;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
/// <summary>
/// Implements importing for archive records
@ -26,30 +26,7 @@ public class ArchiveImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Archive record)
{
var archivePath = await archiveService.GetArchiveFileLocationAsync(record.ObjectKey);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Archives,
Name = record.Version,
};
if (File.Exists(archivePath))
{
var fileInfo = new FileInfo(archivePath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanImport(Archive record) => ImportContext.DataRecord is Data.Models.Game || ImportContext.DataRecord is Data.Models.Redistributable;
public override bool CanExport(Archive record) => ImportContext.DataRecord is Data.Models.Game || ImportContext.DataRecord is Data.Models.Redistributable;
public override async Task<Data.Models.Archive> AddAsync(Archive record)
{
@ -122,23 +99,6 @@ public class ArchiveImporter(
}
}
public override async Task<Archive> ExportAsync(Guid id)
{
var entity = await archiveService.GetAsync(id);
var path = await archiveService.GetArchiveFileLocationAsync(entity);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
using (var fs = fileInfo.OpenRead())
{
ImportContext.Archive.AddEntry($"Archives/{id}", fs, fileInfo.Length, fileInfo.LastWriteTimeUtc);
}
}
return mapper.Map<Archive>(entity);
}
public override async Task<bool> ExistsAsync(Archive archive)
{
if (ImportContext.DataRecord is Data.Models.Game game)

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class CollectionImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class CollectionImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Collection record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Collections,
Name = record.Name,
};
}
public override bool CanImport(Collection record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Collection record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Collection> AddAsync(Collection record)
{
@ -77,11 +68,6 @@ public class CollectionImporter(
}
}
public override async Task<Collection> ExportAsync(Guid id)
{
return await collectionService.GetAsync<Collection>(id);
}
public override async Task<bool> ExistsAsync(Collection record)
{
return await collectionService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class CustomFieldImporter(
IMapper mapper,
@ -18,19 +20,8 @@ public class CustomFieldImporter(
Name = record.Name,
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.GameCustomField record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.CustomFields,
Name = record.Name,
};
}
public override bool CanImport(GameCustomField record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(GameCustomField record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.GameCustomField> AddAsync(GameCustomField record)
{
@ -63,11 +54,6 @@ public class CustomFieldImporter(
}
}
public override async Task<GameCustomField> ExportAsync(Guid id)
{
return await gameCustomFieldService.GetAsync<GameCustomField>(id);
}
public override async Task<bool> ExistsAsync(GameCustomField record)
{
return (await gameService.GetCustomFieldAsync(ImportContext.DataRecord.Id, record.Name)) == null;

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class DeveloperImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class DeveloperImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Company record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Developers,
Name = record.Name,
};
}
public override bool CanImport(Company record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Company record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Company> AddAsync(Company record)
{
@ -86,11 +77,6 @@ public class DeveloperImporter(
}
}
public override async Task<Company> ExportAsync(Guid id)
{
return await companyService.GetAsync<Company>(id);
}
public override async Task<bool> ExistsAsync(Company record)
{
if (ImportContext.DataRecord is not Data.Models.Game game)

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class EngineImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class EngineImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Engine record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Engine,
Name = record.Name,
};
}
public override bool CanImport(Engine record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Engine record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Engine> AddAsync(Engine record)
{
@ -78,11 +69,6 @@ public class EngineImporter(
}
}
public override async Task<Engine> ExportAsync(Guid id)
{
return await engineService.GetAsync<Engine>(id);
}
public override async Task<bool> ExistsAsync(Engine record)
{
return await engineService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,9 +1,10 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class GameImporter(
IMapper mapper,
@ -18,17 +19,7 @@ public class GameImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Game record)
{
return new ExportItemInfo
{
Id = record.Id,
Name = record.Title,
};
}
public override bool CanImport(Game record) => true;
public override bool CanExport(Game record) => true;
public override async Task<Data.Models.Game> AddAsync(Game record)
{
@ -98,31 +89,6 @@ public class GameImporter(
}
}
public override async Task<Game> ExportAsync(Guid id)
{
// Retrieve the game again, but only get the minimal amount of data required
var manifest = await gameService.GetAsync<Game>(id);
manifest.Actions ??= new List<SDK.Models.Manifest.Action>();
manifest.Archives ??= new List<SDK.Models.Manifest.Archive>();
manifest.Collections ??= new List<SDK.Models.Manifest.Collection>();
manifest.CustomFields ??= new List<SDK.Models.Manifest.GameCustomField>();
manifest.Developers ??= new List<SDK.Models.Manifest.Company>();
manifest.Genres ??= new List<SDK.Models.Manifest.Genre>();
manifest.Keys ??= new List<SDK.Models.Manifest.Key>();
manifest.Media ??= new List<SDK.Models.Manifest.Media>();
manifest.MultiplayerModes ??= new List<SDK.Models.Manifest.MultiplayerMode>();
manifest.Platforms ??= new List<SDK.Models.Manifest.Platform>();
manifest.PlaySessions ??= new List<SDK.Models.Manifest.PlaySession>();
manifest.Publishers ??= new List<SDK.Models.Manifest.Company>();
manifest.Saves ??= new List<SDK.Models.Manifest.Save>();
manifest.SavePaths ??= new List<SDK.Models.Manifest.SavePath>();
manifest.Scripts ??= new List<SDK.Models.Manifest.Script>();
manifest.Tags ??= new List<SDK.Models.Manifest.Tag>();
return manifest;
}
public override async Task<bool> ExistsAsync(Game record)
{
return await gameService.ExistsAsync(g => g.Id == record.Id || g.Title == record.Title);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class GenreImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class GenreImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Genre record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Genres,
Name = record.Name,
};
}
public override bool CanImport(Genre record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Genre record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Genre> AddAsync(Genre record)
{
@ -77,11 +68,6 @@ public class GenreImporter(
}
}
public override async Task<Genre> ExportAsync(Guid id)
{
return await genreService.GetAsync<Genre>(id);
}
public override async Task<bool> ExistsAsync(Genre record)
{
return await genreService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,8 +1,11 @@
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;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class KeyImporter(
IMapper mapper,
@ -17,18 +20,7 @@ public class KeyImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Key record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Keys,
Name = new String('*', record.Value.Length),
};
}
public override bool CanImport(Key record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Key record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Key> AddAsync(Key record)
{
@ -74,11 +66,6 @@ public class KeyImporter(
}
}
public override async Task<Key> ExportAsync(Guid id)
{
return await keyService.GetAsync<Key>(id);
}
public override async Task<bool> ExistsAsync(Key record)
{
return await keyService.ExistsAsync(k => k.Value == record.Value);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class MediaImporter(
IMapper mapper,
@ -20,30 +22,7 @@ public class MediaImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Media record)
{
var mediaPath = await mediaService.GetMediaPathAsync(record.Id);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Media,
Name = String.IsNullOrWhiteSpace(record.Name) ? record.Type.ToString() : $"{record.Type} - {record.Name}",
};
if (File.Exists(mediaPath))
{
var fileInfo = new FileInfo(mediaPath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanImport(Media record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Media record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Media> AddAsync(Media record)
{
@ -111,22 +90,6 @@ public class MediaImporter(
}
}
public override async Task<Media> ExportAsync(Guid id)
{
var path = await mediaService.GetMediaPathAsync(id);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
using (var fs = fileInfo.OpenRead())
{
ImportContext.Archive.AddEntry($"Media/{id}", fs);
}
}
return await mediaService.GetAsync<Media>(id);
}
public override Task<bool> ExistsAsync(Media media)
{
return mediaService.ExistsAsync(m => m.Type == media.Type && m.Id == media.Id);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class MultiplayerModeImporter(
IMapper mapper,
@ -18,18 +20,7 @@ public class MultiplayerModeImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.MultiplayerMode record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.MultiplayerModes,
Name = String.IsNullOrWhiteSpace(record.Description) ? record.Type.ToString() : $"{record.Type} - {record.Description}",
};
}
public override bool CanImport(MultiplayerMode record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(MultiplayerMode record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.MultiplayerMode> AddAsync(MultiplayerMode record)
{
@ -81,11 +72,6 @@ public class MultiplayerModeImporter(
}
}
public override async Task<MultiplayerMode> ExportAsync(Guid id)
{
return await multiplayerModeService.GetAsync<MultiplayerMode>(id);
}
public override async Task<bool> ExistsAsync(MultiplayerMode record)
{
var game = ImportContext.DataRecord as Data.Models.Game;

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class PlatformImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class PlatformImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Platform record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Platforms,
Name = record.Name,
};
}
public override bool CanImport(Platform record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Platform record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Platform> AddAsync(Platform record)
{
@ -77,11 +68,6 @@ public class PlatformImporter(
}
}
public override async Task<Platform> ExportAsync(Guid id)
{
return await platformService.GetAsync<Platform>(id);
}
public override async Task<bool> ExistsAsync(Platform record)
{
return await platformService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class PlaySessionImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class PlaySessionImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.PlaySession record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.PlaySessions,
Name = $"{record.User} - {record.Start}-{record.End}",
};
}
public override bool CanImport(PlaySession record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(PlaySession record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.PlaySession> AddAsync(PlaySession record)
{
@ -76,11 +67,6 @@ public class PlaySessionImporter(
}
}
public override async Task<PlaySession> ExportAsync(Guid id)
{
return await playSessionService.GetAsync<PlaySession>(id);
}
public override async Task<bool> ExistsAsync(PlaySession record)
{
var game = ImportContext.DataRecord as Data.Models.Game;

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class PublisherImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class PublisherImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Company record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Publishers,
Name = record.Name,
};
}
public override bool CanImport(Company record) => ImportContext.DataRecord is Data.Models.Company;
public override bool CanExport(Company record) => ImportContext.DataRecord is Data.Models.Company;
public override async Task<Data.Models.Company> AddAsync(Company record)
{
@ -79,11 +70,6 @@ public class PublisherImporter(
}
}
public override async Task<Company> ExportAsync(Guid id)
{
return await companyService.GetAsync<Company>(id);
}
public override async Task<bool> ExistsAsync(Company record)
{
return await companyService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,8 +1,10 @@
using LANCommander.SDK.Models.Manifest;
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class RedistributableImporter(
IMapper mapper,
@ -17,17 +19,7 @@ public class RedistributableImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Redistributable record)
{
return new ExportItemInfo()
{
Id = record.Id,
Name = record.Name,
};
}
public override bool CanImport(Redistributable record) => true;
public override bool CanExport(Redistributable record) => true;
public override async Task<Data.Models.Redistributable> AddAsync(Redistributable record)
{
@ -69,11 +61,6 @@ public class RedistributableImporter(
}
}
public override async Task<Redistributable> ExportAsync(Guid id)
{
return await redistributableService.GetAsync<Redistributable>(id);
}
public override async Task<bool> ExistsAsync(Redistributable record)
{
return await redistributableService.ExistsAsync(r => r.Id == record.Id || r.Name == record.Name);

View file

@ -1,12 +1,13 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using LANCommander.Server.Data.Models;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using SharpCompress.Archives;
using SharpCompress.Common;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
/// <summary>
/// Implements importing for save records
@ -28,30 +29,7 @@ public class SaveImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.GameSave record)
{
var savePath = await gameSaveService.GetSavePathAsync(record.Id);
var info = new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Saves,
Name = $"{record.User} - {record.CreatedOn}",
};
if (File.Exists(savePath))
{
var fileInfo = new FileInfo(savePath);
if (fileInfo.Exists)
info.Size = fileInfo.Length;
}
return info;
}
public override bool CanImport(Save record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Save record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.GameSave> AddAsync(Save record)
{
@ -128,23 +106,6 @@ public class SaveImporter(
}
}
public override async Task<Save> ExportAsync(Guid id)
{
var entity = await gameSaveService.GetAsync<Data.Models.Game>(id);
var path = await gameSaveService.GetSavePathAsync(id);
var fileInfo = new FileInfo(path);
if (fileInfo.Exists)
{
using (var fs = fileInfo.OpenRead())
{
ImportContext.Archive.AddEntry($"Saves/{id}", fs);
}
}
return mapper.Map<Save>(entity);
}
public override async Task<bool> ExistsAsync(Save archive)
{
return await gameSaveService

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class SavePathImporter(
IMapper mapper,
@ -18,18 +20,7 @@ public class SavePathImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.SavePath record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.SavePaths,
Name = record.Path,
};
}
public override bool CanImport(SavePath record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(SavePath record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.SavePath> AddAsync(SavePath record)
{
@ -77,11 +68,6 @@ public class SavePathImporter(
}
}
public override async Task<SavePath> ExportAsync(Guid id)
{
return await savePathService.GetAsync<SavePath>(id);
}
public override async Task<bool> ExistsAsync(SavePath record)
{
return await savePathService.ExistsAsync(p => p.Id == record.Id);

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class ScriptImporter(
IMapper mapper,
@ -19,17 +21,6 @@ public class ScriptImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Script record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Scripts,
Name = record.Name,
Size = record.Contents.Length,
};
}
public override bool CanImport(Script record) =>
ImportContext.DataRecord is Data.Models.Game
||
@ -37,13 +28,6 @@ public class ScriptImporter(
||
ImportContext.DataRecord is Data.Models.Server;
public override bool CanExport(Script record) =>
ImportContext.DataRecord is Data.Models.Game
||
ImportContext.DataRecord is Data.Models.Redistributable
||
ImportContext.DataRecord is Data.Models.Server;
public override async Task<Data.Models.Script> AddAsync(Script record)
{
var archiveEntry = ImportContext.Archive.Entries.FirstOrDefault(e => e.Key == $"Scripts/{record.Id}");
@ -126,24 +110,6 @@ public class ScriptImporter(
}
}
public override async Task<Script> ExportAsync(Guid id)
{
var entity = await scriptService.GetAsync<Script>(id);
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream))
{
writer.Write(entity.Contents);
writer.Flush();
stream.Position = 0;
ImportContext.Archive.AddEntry($"Scripts/{entity.Id}", stream);
}
return mapper.Map<Script>(entity);
}
public override async Task<bool> ExistsAsync(Script record)
{
if (ImportContext.DataRecord is Data.Models.Game game)

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class ServerConsoleImporter(
IMapper mapper,
@ -18,18 +20,7 @@ public class ServerConsoleImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.ServerConsole record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.ServerConsoles,
Name = record.Name,
};
}
public override bool CanImport(ServerConsole record) => ImportContext.DataRecord is Data.Models.ServerConsole;
public override bool CanExport(ServerConsole record) => ImportContext.DataRecord is Data.Models.ServerConsole;
public override async Task<Data.Models.ServerConsole> AddAsync(ServerConsole record)
{
@ -79,11 +70,6 @@ public class ServerConsoleImporter(
}
}
public override async Task<ServerConsole> ExportAsync(Guid id)
{
return await serverConsoleService.GetAsync<ServerConsole>(id);
}
public override async Task<bool> ExistsAsync(ServerConsole record)
{
return await serverConsoleService

View file

@ -1,9 +1,11 @@
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Models.Manifest;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class ServerHttpPathImporter(
IMapper mapper,
@ -19,18 +21,7 @@ public class ServerHttpPathImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.ServerHttpPath record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.ServerHttpPaths,
Name = record.Path,
};
}
public override bool CanImport(ServerHttpPath record) => ImportContext.DataRecord is Data.Models.Server;
public override bool CanExport(ServerHttpPath record) => ImportContext.DataRecord is Data.Models.Server;
public override async Task<Data.Models.ServerHttpPath> AddAsync(ServerHttpPath record)
{
@ -75,12 +66,6 @@ public class ServerHttpPathImporter(
}
}
public override async Task<ServerHttpPath> ExportAsync(Guid id)
{
// Include all files?
return await serverHttpPathService.GetAsync<ServerHttpPath>(id);
}
public override async Task<bool> ExistsAsync(ServerHttpPath record)
{
return await serverHttpPathService

View file

@ -1,9 +1,11 @@
using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using LANCommander.Server.ImportExport.Exceptions;
using LANCommander.Server.ImportExport.Models;
using LANCommander.Server.Services;
using SharpCompress.Archives;
using SharpCompress.Common;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class ServerImporter(
IMapper mapper,
@ -22,29 +24,7 @@ public class ServerImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Server record)
{
var files = Directory.GetFiles(record.WorkingDirectory, "*", SearchOption.AllDirectories);
long size = 0;
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
size += fileInfo.Length;
}
return new ExportItemInfo
{
Id = record.Id,
Name = record.Name,
Size = size
};
}
public override bool CanImport(SDK.Models.Manifest.Server record) => true;
public override bool CanExport(SDK.Models.Manifest.Server record) => true;
public override async Task<Data.Models.Server> AddAsync(SDK.Models.Manifest.Server record)
{
@ -99,26 +79,6 @@ public class ServerImporter(
}
}
public override async Task<SDK.Models.Manifest.Server> ExportAsync(Guid id)
{
var entity = await serverService.GetAsync<SDK.Models.Manifest.Server>(id);
var files = Directory.GetFiles(entity.WorkingDirectory, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
using (var fs = fileInfo.OpenRead())
{
// Probably need to handle working directory
ImportContext.Archive.AddEntry($"Files/{fileInfo.Name}", fs);
}
}
return mapper.Map<SDK.Models.Manifest.Server>(entity);
}
public override async Task<bool> ExistsAsync(SDK.Models.Manifest.Server record)
{
return await serverService.ExistsAsync(s => s.Id == record.Id || s.Name == record.Name);

View file

@ -1,8 +1,11 @@
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;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
public class TagImporter(
IMapper mapper,
@ -18,18 +21,7 @@ public class TagImporter(
};
}
public override async Task<ExportItemInfo> GetExportInfoAsync(Data.Models.Tag record)
{
return new ExportItemInfo
{
Id = record.Id,
Flag = ImportRecordFlags.Tags,
Name = record.Name,
};
}
public override bool CanImport(Tag record) => ImportContext.DataRecord is Data.Models.Game;
public override bool CanExport(Tag record) => ImportContext.DataRecord is Data.Models.Game;
public override async Task<Data.Models.Tag> AddAsync(Tag record)
{
@ -76,11 +68,6 @@ public class TagImporter(
}
}
public override async Task<Tag> ExportAsync(Guid id)
{
return await tagService.GetAsync<Tag>(id);
}
public override async Task<bool> ExistsAsync(Tag record)
{
return await tagService.ExistsAsync(c => c.Name == record.Name);

View file

@ -1,4 +1,6 @@
namespace LANCommander.Server.Services.Importers;
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.ImportExport.Importers;
public abstract class BaseImporter<TRecord, TEntity> : IImporter<TRecord, TEntity>
{
@ -10,11 +12,8 @@ public abstract class BaseImporter<TRecord, TEntity> : IImporter<TRecord, TEntit
}
public abstract Task<ImportItemInfo> GetImportInfoAsync(TRecord record);
public abstract Task<ExportItemInfo> GetExportInfoAsync(TEntity record);
public abstract bool CanImport(TRecord record);
public abstract bool CanExport(TRecord record);
public abstract Task<TEntity> AddAsync(TRecord record);
public abstract Task<TEntity> UpdateAsync(TRecord record);
public abstract Task<TRecord> ExportAsync(Guid id);
public abstract Task<bool> ExistsAsync(TRecord record);
}

View file

@ -1,7 +1,6 @@
using System.IO.Compression;
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
using LANCommander.Server.ImportExport.Models;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Importers;
/// <summary>
/// Implementations should be able to process a single record. This will be called
@ -13,11 +12,8 @@ public interface IImporter<TRecord, TEntity>
{
void UseContext(ImportContext context);
Task<ImportItemInfo> GetImportInfoAsync(TRecord record);
Task<ExportItemInfo> GetExportInfoAsync(TEntity record);
bool CanImport(TRecord record);
bool CanExport(TRecord record);
Task<TEntity> AddAsync(TRecord record);
Task<TEntity> UpdateAsync(TRecord record);
Task<TRecord> ExportAsync(Guid id);
Task<bool> ExistsAsync(TRecord record);
}

View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LANCommander.Server.Services\LANCommander.Server.Services.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,23 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.ImportExport.Models;
public class ExportQueueItem
{
public Guid Id { get; set; }
public ExportRecordFlags Type { get; set; }
public object Record { get; set; }
public bool Processed { get; set; }
public ExportQueueItem(ExportRecordFlags type, object record)
{
Type = type;
Record = record;
}
public ExportQueueItem(Guid id, ExportRecordFlags type, object record)
{
Type = type;
Record = record;
}
}

View file

@ -1,11 +1,12 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Models;
public class ExportItemInfo
{
public Guid Id { get; set; }
public ImportRecordFlags Flag { get; set; }
public bool Selected { get; set; }
public ExportRecordFlags Flag { get; set; }
public string Name { get; set; }
public long Size { get; set; }
}

View file

@ -1,9 +1,10 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Models;
public class ImportItemInfo
{
public bool Selected { get; set; }
public ImportRecordFlags Flag { get; set; }
public string Name { get; set; }
public long Size { get; set; }

View file

@ -1,6 +1,6 @@
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Services.Importers;
namespace LANCommander.Server.ImportExport.Models;
public class ImportQueueItem
{

View file

@ -0,0 +1,40 @@
using LANCommander.Server.ImportExport.Importers;
namespace LANCommander.Server.ImportExport.Services;
public class ExportService : IDisposable
{
private Dictionary<Guid, ExportContext> ExportContexts = new();
public Guid EnqueueContext(ExportContext context)
{
var id = Guid.NewGuid();
ExportContexts.Add(id, context);
return id;
}
public ExportContext GetContext(Guid id)
{
if (ExportContexts.TryGetValue(id, out var context))
return context;
return null;
}
public async Task ExportAsync(Guid contextId, Stream outputStream)
{
if (ExportContexts.TryGetValue(contextId, out var context))
{
await context.ExportQueueAsync(outputStream);
ExportContexts.Remove(contextId);
}
}
public void Dispose()
{
// TODO release managed resources here
}
}

View file

@ -1,6 +1,6 @@
using LANCommander.Server.Services.Importers;
using LANCommander.Server.ImportExport.Importers;
namespace LANCommander.Server.Services;
namespace LANCommander.Server.ImportExport.Services;
public class ImportService : IDisposable
{
@ -23,16 +23,6 @@ public class ImportService : IDisposable
return null;
}
public async Task ExportAsync(Guid contextId, Stream outputStream)
{
if (ImportContexts.TryGetValue(contextId, out var context))
{
await context.ExportQueueAsync(outputStream);
ImportContexts.Remove(contextId);
}
}
public void Dispose()
{
// TODO release managed resources here

View file

@ -2,7 +2,6 @@ using LANCommander.SDK;
using LANCommander.SDK.Models;
using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Factories;
using LANCommander.Server.Services.Importers;
using LANCommander.Server.Services.MediaGrabbers;
using LANCommander.Server.Services.ServerEngines;
using Microsoft.Extensions.DependencyInjection;
@ -50,38 +49,7 @@ public static class IServiceCollectionExtensions
services.AddScoped<RoleService>();
services.AddScoped<UserCustomFieldService>();
services.AddScoped<GameCustomFieldService>();
services.AddScoped<AuthenticationService>();
services.AddSingleton<ImportService>();
services.AddTransient<SetupService>();
// Register importers
services.AddScoped<ImportContextFactory>();
services.AddScoped<ImportContext>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game>, GameImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable>, RedistributableImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server>, ServerImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action>, ActionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive>, ArchiveImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection>, CollectionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField>, CustomFieldImporter>();
services.AddScoped<DeveloperImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection>, CollectionImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine>, EngineImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre>, GenreImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key>, KeyImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media>, MediaImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode>, MultiplayerModeImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform>, PlatformImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession>, PlaySessionImporter>();
services.AddScoped<PublisherImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave>, SaveImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath>, SavePathImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script>, ScriptImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole>, ServerConsoleImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath>, ServerHttpPathImporter>();
services.AddScoped<BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag>, TagImporter>();
// Register server engines
services.AddSingleton<IServerEngine, LocalServerEngine>();

View file

@ -0,0 +1,6 @@
namespace LANCommander.Server.Services.Extensions;
public class JsonSerializerExtensions
{
}

View file

@ -1,923 +0,0 @@
using System.IO.Compression;
using AutoMapper;
using LANCommander.SDK.Enums;
using LANCommander.SDK.Helpers;
using LANCommander.Server.Data.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
namespace LANCommander.Server.Services.Importers;
public class ImportContext(
BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game> gameImporter,
BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> redistributableImporter,
BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server> serverImporter,
GameService gameService,
RedistributableService redistributableService,
ServerService serverService,
ArchiveService archiveService,
MediaService mediaService,
GameSaveService saveService,
BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action> actionImporter,
BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive> archiveImporter,
BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection> collectionImporter,
BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> customFieldImporter,
DeveloperImporter developerImporter,
PublisherImporter publisherImporter,
BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine> engineImporter,
BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre> genreImporter,
BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key> keyImporter,
BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media> mediaImporter,
BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> multiplayerModeImporter,
BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform> platformImporter,
BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> playSessionImporter,
BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave> saveImporter,
BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> savePathImporter,
BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script> scriptImporter,
BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> serverConsoleImporter,
BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> serverHttpPathImporter,
BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag> tagImporter,
IMapper mapper,
ILogger<ImportContext> logger) : IDisposable
{
public object Manifest { get; private set; }
public BaseModel DataRecord { get; private set; }
public StorageLocation ArchiveStorageLocation { get; }
public ZipArchive Archive { get; private set; }
public BaseImporter<SDK.Models.Manifest.Game, Data.Models.Game> Games { get; private set; } = gameImporter;
public BaseImporter<SDK.Models.Manifest.Redistributable, Data.Models.Redistributable> Redistributables { get; private set; } = redistributableImporter;
public BaseImporter<SDK.Models.Manifest.Server, Data.Models.Server> Servers { get; private set; } = serverImporter;
public BaseImporter<SDK.Models.Manifest.Action, Data.Models.Action> Actions = actionImporter;
public BaseImporter<SDK.Models.Manifest.Archive, Data.Models.Archive> Archives = archiveImporter;
public BaseImporter<SDK.Models.Manifest.Collection, Data.Models.Collection> Collections = collectionImporter;
public BaseImporter<SDK.Models.Manifest.GameCustomField, Data.Models.GameCustomField> CustomFields = customFieldImporter;
public BaseImporter<SDK.Models.Manifest.Company, Data.Models.Company> Developers = developerImporter;
public BaseImporter<SDK.Models.Manifest.Engine, Data.Models.Engine> Engines = engineImporter;
public BaseImporter<SDK.Models.Manifest.Genre, Data.Models.Genre> Genres = genreImporter;
public BaseImporter<SDK.Models.Manifest.Key, Data.Models.Key> Keys = keyImporter;
public BaseImporter<SDK.Models.Manifest.Media, Data.Models.Media> Media = mediaImporter;
public BaseImporter<SDK.Models.Manifest.MultiplayerMode, Data.Models.MultiplayerMode> MultiplayerModes = multiplayerModeImporter;
public BaseImporter<SDK.Models.Manifest.Platform, Data.Models.Platform> Platforms = platformImporter;
public BaseImporter<SDK.Models.Manifest.PlaySession, Data.Models.PlaySession> PlaySessions = playSessionImporter;
public BaseImporter<SDK.Models.Manifest.Company, Data.Models.Company> Publishers = publisherImporter;
public BaseImporter<SDK.Models.Manifest.Save, Data.Models.GameSave> Saves = saveImporter;
public BaseImporter<SDK.Models.Manifest.SavePath, Data.Models.SavePath> SavePaths = savePathImporter;
public BaseImporter<SDK.Models.Manifest.Script, Data.Models.Script> Scripts = scriptImporter;
public BaseImporter<SDK.Models.Manifest.ServerConsole, Data.Models.ServerConsole> ServerConsoles = serverConsoleImporter;
public BaseImporter<SDK.Models.Manifest.ServerHttpPath, Data.Models.ServerHttpPath> ServerHttpPaths = serverHttpPathImporter;
public BaseImporter<SDK.Models.Manifest.Tag, Data.Models.Tag> Tags = tagImporter;
public int Remaining => _queue.Count;
public int Processed => _queue.Count(qi => qi.Processed);
public int Total => _queue.Count;
private List<ImportQueueItem> _queue { get; } = new();
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);
}
#region Initialize Import
public async Task<IEnumerable<ImportItemInfo>> InitializeImportAsync(string archivePath)
{
UseContext(this);
Archive = ZipArchive.Open(archivePath);
var manifestEntry = Archive.Entries.FirstOrDefault(e => e.Key == ManifestHelper.ManifestFilename);
using (var reader = new StreamReader(manifestEntry.OpenEntryStream()))
{
var manifestContents = await reader.ReadToEndAsync();
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Game>(manifestContents, out var gameManifest))
return await InitializeGameImportAsync(gameManifest);
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Redistributable>(manifestContents, out var redistributableManifest))
return await InitializeRedistributableImportAsync(redistributableManifest);
if (ManifestHelper.TryDeserialize<SDK.Models.Manifest.Server>(manifestContents, out var serverManifest))
return await InitializeServerImportAsync(serverManifest);
throw new InvalidOperationException("Unknown manifest file");
}
}
private async Task<IEnumerable<ImportItemInfo>> InitializeGameImportAsync(SDK.Models.Manifest.Game gameManifest)
{
Manifest = gameManifest;
var importItemInfo = new List<ImportItemInfo>();
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.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)
{
Manifest = redistributableManifest;
var importItemInfo = new List<ImportItemInfo>();
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)
{
Manifest = serverManifest;
var importItemInfo = new List<ImportItemInfo>();
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 Initialize Export
public async Task<IEnumerable<ExportItemInfo>> InitializeExportAsync(Guid recordId, ExportRecordType recordType)
{
UseContext(this);
switch (recordType)
{
case ExportRecordType.Game:
return await InitializeGameExportAsync(recordId);
case ExportRecordType.Redistributable:
return await InitializeRedistributableExportAsync(recordId);
case ExportRecordType.Server:
return await InitializeServerExportAsync(recordId);
default:
throw new InvalidOperationException("Unknown record type");
}
}
private async Task<IEnumerable<ExportItemInfo>> InitializeGameExportAsync(Guid gameId)
{
var game = await gameService.Query(q =>
{
return q
.AsNoTracking()
.AsSplitQuery()
.Include(g => g.Actions)
.Include(g => g.Archives)
.Include(g => g.BaseGame)
.Include(g => g.Categories)
.Include(g => g.Collections)
.Include(g => g.CustomFields)
.Include(g => g.DependentGames)
.Include(g => g.Developers)
.Include(g => g.Engine)
.Include(g => g.Genres)
.Include(g => g.Media)
.Include(g => g.MultiplayerModes)
.Include(g => g.Platforms)
.Include(g => g.Publishers)
.Include(g => g.Redistributables)
.Include(g => g.SavePaths)
.Include(g => g.Tags);
}).GetAsync(gameId);
var gameManifest = mapper.Map<SDK.Models.Manifest.Game>(game);
DataRecord = game;
Manifest = gameManifest;
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Actions, Actions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Archives, Archives).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Collections, Collections).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.CustomFields, CustomFields).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Developers, Developers).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync([game.Engine], Engines).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Genres, Genres).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Keys, Keys).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Media, Media).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.MultiplayerModes, MultiplayerModes).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Platforms, Platforms).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.PlaySessions, PlaySessions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Publishers, Publishers).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.GameSaves, Saves).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.SavePaths, SavePaths).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Scripts, Scripts).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(game.Tags, Tags).ToListAsync());
return exportItemInfo;
}
private async Task<IEnumerable<ExportItemInfo>> InitializeRedistributableExportAsync(Guid redistributableId)
{
var redistributable = await redistributableService
.AsNoTracking()
.AsSplitQuery()
.Query(q =>
{
return q
.Include(r => r.Archives)
.Include(r => r.Scripts);
})
.GetAsync(redistributableId);
Manifest = mapper.Map<SDK.Models.Manifest.Redistributable>(redistributable);
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(redistributable.Archives, Archives).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(redistributable.Scripts, Scripts).ToListAsync());
return exportItemInfo;
}
private async Task<IEnumerable<ExportItemInfo>> InitializeServerExportAsync(Guid serverId)
{
var server = await serverService
.AsNoTracking()
.AsSplitQuery()
.Query(q =>
{
return q
.Include(s => s.Actions)
.Include(s => s.HttpPaths)
.Include(s => s.ServerConsoles)
.Include(s => s.Scripts);
})
.GetAsync(serverId);
Manifest = mapper.Map<SDK.Models.Manifest.Server>(server);
var exportItemInfo = new List<ExportItemInfo>();
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.Actions, Actions).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.Scripts, Scripts).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.ServerConsoles, ServerConsoles).ToListAsync());
exportItemInfo.AddRange(await GetExportItemInfoAsync(server.HttpPaths, ServerHttpPaths).ToListAsync());
return exportItemInfo;
}
#endregion
#region Prepare Import Queue
public async Task PrepareImportQueueAsync(ImportRecordFlags importRecordFlags)
{
if (Manifest is SDK.Models.Manifest.Game gameManifest)
await PrepareGameImportQueueAsync(gameManifest, importRecordFlags);
if (Manifest is SDK.Models.Manifest.Redistributable redistributableManifest)
await PrepareRedistributableImportQueueAsync(redistributableManifest, importRecordFlags);
if (Manifest is SDK.Models.Manifest.Server serverManifest)
await PrepareServerImportQueueAsync(serverManifest, importRecordFlags);
}
public async Task PrepareGameImportQueueAsync(SDK.Models.Manifest.Game gameManifest, ImportRecordFlags importRecordFlags)
{
if (!(await gameImporter.ExistsAsync(gameManifest)))
DataRecord = await gameImporter.AddAsync(gameManifest);
else
DataRecord = await gameImporter.UpdateAsync(gameManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToImportQueueAsync(ImportRecordFlags.Actions, gameManifest.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToImportQueueAsync(ImportRecordFlags.Archives, gameManifest.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Collections))
await AddToImportQueueAsync(ImportRecordFlags.Collections, gameManifest.Collections);
if (importRecordFlags.HasFlag(ImportRecordFlags.CustomFields))
await AddToImportQueueAsync(ImportRecordFlags.CustomFields, gameManifest.CustomFields);
if (importRecordFlags.HasFlag(ImportRecordFlags.Developers))
await AddToImportQueueAsync(ImportRecordFlags.Developers, gameManifest.Developers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Engine) && gameManifest.Engine != null)
await AddToImportQueueAsync(ImportRecordFlags.Engine, [gameManifest.Engine]);
if (importRecordFlags.HasFlag(ImportRecordFlags.Genres))
await AddToImportQueueAsync(ImportRecordFlags.Genres, gameManifest.Genres);
if (importRecordFlags.HasFlag(ImportRecordFlags.Keys))
await AddToImportQueueAsync(ImportRecordFlags.Keys, gameManifest.Keys);
if (importRecordFlags.HasFlag(ImportRecordFlags.Media))
await AddToImportQueueAsync(ImportRecordFlags.Media, gameManifest.Media);
if (importRecordFlags.HasFlag(ImportRecordFlags.MultiplayerModes))
await AddToImportQueueAsync(ImportRecordFlags.MultiplayerModes, gameManifest.MultiplayerModes);
if (importRecordFlags.HasFlag(ImportRecordFlags.Platforms))
await AddToImportQueueAsync(ImportRecordFlags.Platforms, gameManifest.Platforms);
if (importRecordFlags.HasFlag(ImportRecordFlags.PlaySessions))
await AddToImportQueueAsync(ImportRecordFlags.PlaySessions, gameManifest.PlaySessions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Publishers))
await AddToImportQueueAsync(ImportRecordFlags.Publishers, gameManifest.Publishers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Saves))
await AddToImportQueueAsync(ImportRecordFlags.Saves, gameManifest.Saves);
if (importRecordFlags.HasFlag(ImportRecordFlags.SavePaths))
await AddToImportQueueAsync(ImportRecordFlags.SavePaths, gameManifest.SavePaths);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, gameManifest.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.Tags))
await AddToImportQueueAsync(ImportRecordFlags.Tags, gameManifest.Tags);
}
public async Task PrepareRedistributableImportQueueAsync(SDK.Models.Manifest.Redistributable redistributableManifest,
ImportRecordFlags importRecordFlags)
{
if (!(await redistributableImporter.ExistsAsync(redistributableManifest)))
DataRecord = await redistributableImporter.AddAsync(redistributableManifest);
else
DataRecord = await redistributableImporter.UpdateAsync(redistributableManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToImportQueueAsync(ImportRecordFlags.Archives, redistributableManifest.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, redistributableManifest.Scripts);
}
public async Task PrepareServerImportQueueAsync(SDK.Models.Manifest.Server serverManifest,
ImportRecordFlags importRecordFlags)
{
if (!(await serverImporter.ExistsAsync(serverManifest)))
DataRecord = await serverImporter.AddAsync(serverManifest);
else
DataRecord = await serverImporter.UpdateAsync(serverManifest);
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToImportQueueAsync(ImportRecordFlags.Actions, serverManifest.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToImportQueueAsync(ImportRecordFlags.Scripts, serverManifest.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerConsoles))
await AddToImportQueueAsync(ImportRecordFlags.ServerConsoles, serverManifest.ServerConsoles);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerHttpPaths))
await AddToImportQueueAsync(ImportRecordFlags.ServerHttpPaths, serverManifest.HttpPaths);
}
#endregion
#region Prepare Export Queue
public async Task PrepareExportQueueAsync(ImportRecordFlags importRecordFlags)
{
if (DataRecord is Data.Models.Game game)
await PrepareGameExportQueueAsync(game, importRecordFlags);
if (DataRecord is Data.Models.Redistributable redistributable)
await PrepareRedistributableExportQueueAsync(redistributable, importRecordFlags);
if (DataRecord is Data.Models.Server server)
await PrepareServerExportQueueAsync(server, importRecordFlags);
}
public async Task PrepareGameExportQueueAsync(Game game, ImportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToExportQueueAsync(ImportRecordFlags.Actions, game.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToExportQueueAsync(ImportRecordFlags.Archives, game.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Collections))
await AddToExportQueueAsync(ImportRecordFlags.Collections, game.Collections);
if (importRecordFlags.HasFlag(ImportRecordFlags.CustomFields))
await AddToExportQueueAsync(ImportRecordFlags.CustomFields, game.CustomFields);
if (importRecordFlags.HasFlag(ImportRecordFlags.Developers))
await AddToExportQueueAsync(ImportRecordFlags.Developers, game.Developers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Engine) && game.Engine != null)
await AddToExportQueueAsync(ImportRecordFlags.Engine, [game.Engine]);
if (importRecordFlags.HasFlag(ImportRecordFlags.Genres))
await AddToExportQueueAsync(ImportRecordFlags.Genres, game.Genres);
if (importRecordFlags.HasFlag(ImportRecordFlags.Keys))
await AddToExportQueueAsync(ImportRecordFlags.Keys, game.Keys);
if (importRecordFlags.HasFlag(ImportRecordFlags.Media))
await AddToExportQueueAsync(ImportRecordFlags.Media, game.Media);
if (importRecordFlags.HasFlag(ImportRecordFlags.MultiplayerModes))
await AddToExportQueueAsync(ImportRecordFlags.MultiplayerModes, game.MultiplayerModes);
if (importRecordFlags.HasFlag(ImportRecordFlags.Platforms))
await AddToExportQueueAsync(ImportRecordFlags.Platforms, game.Platforms);
if (importRecordFlags.HasFlag(ImportRecordFlags.PlaySessions))
await AddToExportQueueAsync(ImportRecordFlags.PlaySessions, game.PlaySessions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Publishers))
await AddToExportQueueAsync(ImportRecordFlags.Publishers, game.Publishers);
if (importRecordFlags.HasFlag(ImportRecordFlags.Saves))
await AddToExportQueueAsync(ImportRecordFlags.Saves, game.GameSaves);
if (importRecordFlags.HasFlag(ImportRecordFlags.SavePaths))
await AddToExportQueueAsync(ImportRecordFlags.SavePaths, game.SavePaths);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToExportQueueAsync(ImportRecordFlags.Scripts, game.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.Tags))
await AddToExportQueueAsync(ImportRecordFlags.Tags, game.Tags);
}
public async Task PrepareRedistributableExportQueueAsync(Data.Models.Redistributable redistributable,
ImportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ImportRecordFlags.Archives))
await AddToExportQueueAsync(ImportRecordFlags.Archives, redistributable.Archives);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToExportQueueAsync(ImportRecordFlags.Scripts, redistributable.Scripts);
}
public async Task PrepareServerExportQueueAsync(Data.Models.Server server,
ImportRecordFlags importRecordFlags)
{
if (importRecordFlags.HasFlag(ImportRecordFlags.Actions))
await AddToExportQueueAsync(ImportRecordFlags.Actions, server.Actions);
if (importRecordFlags.HasFlag(ImportRecordFlags.Scripts))
await AddToExportQueueAsync(ImportRecordFlags.Scripts, server.Scripts);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerConsoles))
await AddToExportQueueAsync(ImportRecordFlags.ServerConsoles, server.ServerConsoles);
if (importRecordFlags.HasFlag(ImportRecordFlags.ServerHttpPaths))
await AddToExportQueueAsync(ImportRecordFlags.ServerHttpPaths, server.HttpPaths);
}
#endregion
private async Task AddToExportQueueAsync<TEntity>(ImportRecordFlags type, IEnumerable<TEntity> records) where TEntity : Data.Models.BaseModel
{
if (records != null)
_queue.AddRange(records.Select(r => new ImportQueueItem(r.Id, type, r)));
}
private async Task AddToImportQueueAsync<TRecord>(ImportRecordFlags type, IEnumerable<TRecord> records) where TRecord : SDK.Models.Manifest.BaseModel
{
if (records != null)
_queue.AddRange(records.Select(r => new ImportQueueItem(type, r)));
}
private async Task AddToQueueAsync(Guid id, ImportRecordFlags type, object record)
{
if (record != null)
_queue.Add(new ImportQueueItem(id, type, record));
}
public async Task ImportQueueAsync()
{
foreach (var queueItem in _queue)
{
if (queueItem.Type == ImportRecordFlags.Actions)
await ImportRecordAsync(queueItem, Actions);
else if (queueItem.Type == ImportRecordFlags.Archives)
await ImportRecordAsync(queueItem, Archives);
else if (queueItem.Type == ImportRecordFlags.Collections)
await ImportRecordAsync(queueItem, Collections);
else if (queueItem.Type == ImportRecordFlags.CustomFields)
await ImportRecordAsync(queueItem, CustomFields);
else if (queueItem.Type == ImportRecordFlags.Developers)
await ImportRecordAsync(queueItem, Developers);
else if (queueItem.Type == ImportRecordFlags.Publishers)
await ImportRecordAsync(queueItem, Publishers);
else if (queueItem.Type == ImportRecordFlags.Engine)
await ImportRecordAsync(queueItem, Engines);
else if (queueItem.Type == ImportRecordFlags.Genres)
await ImportRecordAsync(queueItem, Genres);
else if (queueItem.Type == ImportRecordFlags.Keys)
await ImportRecordAsync(queueItem, Keys);
else if (queueItem.Type == ImportRecordFlags.Media)
await ImportRecordAsync(queueItem, Media);
else if (queueItem.Type == ImportRecordFlags.MultiplayerModes)
await ImportRecordAsync(queueItem, MultiplayerModes);
else if (queueItem.Type == ImportRecordFlags.Platforms)
await ImportRecordAsync(queueItem, Platforms);
else if (queueItem.Type == ImportRecordFlags.PlaySessions)
await ImportRecordAsync(queueItem, PlaySessions);
else if (queueItem.Type == ImportRecordFlags.Saves)
await ImportRecordAsync(queueItem, Saves);
else if (queueItem.Type == ImportRecordFlags.SavePaths)
await ImportRecordAsync(queueItem, SavePaths);
else if (queueItem.Type == ImportRecordFlags.Scripts)
await ImportRecordAsync(queueItem, Scripts);
else if (queueItem.Type == ImportRecordFlags.ServerConsoles)
await ImportRecordAsync(queueItem, ServerConsoles);
else if (queueItem.Type == ImportRecordFlags.ServerHttpPaths)
await ImportRecordAsync(queueItem, ServerHttpPaths);
else if (queueItem.Type == ImportRecordFlags.Tags)
await ImportRecordAsync(queueItem, Tags);
}
}
/// <summary>
/// Process the context's data record and write the archive to the supplied stream
/// </summary>
/// <param name="stream">Output stream for the resulting archive to be written to</param>
public async Task ExportQueueAsync(Stream stream)
{
SDK.Models.Manifest.Game gameManifest = null;
SDK.Models.Manifest.Redistributable redistributableManifest = null;
SDK.Models.Manifest.Server serverManifest = null;
if (DataRecord is Data.Models.Game game)
gameManifest = await ExportGameQueueAsync(game);
else if (DataRecord is Data.Models.Redistributable redistributable)
redistributableManifest = await ExportRedistributableQueueAsync(redistributable);
else if (DataRecord is Data.Models.Server server)
serverManifest = await ExportServerQueueAsync(server);
using (var export = new System.IO.Compression.ZipArchive(stream, ZipArchiveMode.Create))
{
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms))
{
if (gameManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(gameManifest));
else if (redistributableManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(redistributableManifest));
else if (serverManifest != null)
await writer.WriteAsync(ManifestHelper.Serialize(serverManifest));
await writer.FlushAsync();
var manifestEntry = export.CreateEntry(ManifestHelper.ManifestFilename, CompressionLevel.NoCompression);
await using (var entryStream = manifestEntry.Open())
{
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(entryStream);
}
}
#region Add Game Files
if (gameManifest != null)
{
if (gameManifest.Archives != null)
foreach (var archive in gameManifest.Archives)
if (archive != null)
await AddArchiveToExport(archive, export);
if (gameManifest.Media != null)
foreach (var media in gameManifest.Media)
if (media != null)
await AddMediaToExport(media, export);
if (gameManifest.Saves != null)
foreach (var save in gameManifest.Saves)
if (save != null)
await AddSaveToExport(save, export);
if (gameManifest.Scripts != null)
foreach (var script in gameManifest.Scripts)
if (script != null)
await AddScriptToExport(script, export);
}
#endregion
#region Add Redistributable Files
if (redistributableManifest != null)
{
if (redistributableManifest.Archives != null)
foreach (var archive in redistributableManifest.Archives)
if (archive != null)
await AddArchiveToExport(archive, export);
if (redistributableManifest.Scripts != null)
foreach (var script in redistributableManifest.Scripts)
if (script != null)
await AddScriptToExport(script, export);
}
#endregion
#region Add Server Files
#warning TODO: Expand this out, add all files from the server's root directory.
// The server's root directory may be different from its working directory. The server model
// should be expanded upon to add this property. When the root directory is changed, the
// working directory should be changed as well.
//
// Should the files from the HTTP paths be included? Maybe the files for those need to be
// added as another export flag.
#endregion
}
}
public async Task<SDK.Models.Manifest.Game> ExportGameQueueAsync(Data.Models.Game game)
{
var manifest = await Games.ExportAsync(game.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ImportRecordFlags.Actions)
manifest.Actions.Add(await ExportRecordAsync(queueItem, Actions));
else if (queueItem.Type == ImportRecordFlags.Archives)
manifest.Archives.Add(await ExportRecordAsync(queueItem, Archives));
else if (queueItem.Type == ImportRecordFlags.Collections)
manifest.Collections.Add(await ExportRecordAsync(queueItem, Collections));
else if (queueItem.Type == ImportRecordFlags.CustomFields)
manifest.CustomFields.Add(await ExportRecordAsync(queueItem, CustomFields));
else if (queueItem.Type == ImportRecordFlags.Developers)
manifest.Developers.Add(await ExportRecordAsync(queueItem, Developers));
else if (queueItem.Type == ImportRecordFlags.Publishers)
manifest.Publishers.Add(await ExportRecordAsync(queueItem, Publishers));
else if (queueItem.Type == ImportRecordFlags.Engine)
manifest.Engine = await ExportRecordAsync(queueItem, Engines);
else if (queueItem.Type == ImportRecordFlags.Genres)
manifest.Genres.Add(await ExportRecordAsync(queueItem, Genres));
else if (queueItem.Type == ImportRecordFlags.Keys)
manifest.Keys.Add(await ExportRecordAsync(queueItem, Keys));
else if (queueItem.Type == ImportRecordFlags.Media)
manifest.Media.Add(await ExportRecordAsync(queueItem, Media));
else if (queueItem.Type == ImportRecordFlags.MultiplayerModes)
manifest.MultiplayerModes.Add(await ExportRecordAsync(queueItem, MultiplayerModes));
else if (queueItem.Type == ImportRecordFlags.Platforms)
manifest.Platforms.Add(await ExportRecordAsync(queueItem, Platforms));
else if (queueItem.Type == ImportRecordFlags.PlaySessions)
manifest.PlaySessions.Add(await ExportRecordAsync(queueItem, PlaySessions));
else if (queueItem.Type == ImportRecordFlags.Saves)
manifest.Saves.Add(await ExportRecordAsync(queueItem, Saves));
else if (queueItem.Type == ImportRecordFlags.SavePaths)
manifest.SavePaths.Add(await ExportRecordAsync(queueItem, SavePaths));
else if (queueItem.Type == ImportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
else if (queueItem.Type == ImportRecordFlags.Tags)
manifest.Tags.Add(await ExportRecordAsync(queueItem, Tags));
}
return manifest;
}
public async Task<SDK.Models.Manifest.Redistributable> ExportRedistributableQueueAsync(Data.Models.Redistributable redistributable)
{
var manifest = await Redistributables.ExportAsync(redistributable.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ImportRecordFlags.Archives)
manifest.Archives.Add(await ExportRecordAsync(queueItem, Archives));
else if (queueItem.Type == ImportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
}
return manifest;
}
public async Task<SDK.Models.Manifest.Server> ExportServerQueueAsync(Data.Models.Server server)
{
var manifest = await Servers.ExportAsync(server.Id);
foreach (var queueItem in _queue)
{
if (queueItem.Type == ImportRecordFlags.Actions)
manifest.Actions.Add(await ExportRecordAsync(queueItem, Actions));
else if (queueItem.Type == ImportRecordFlags.Scripts)
manifest.Scripts.Add(await ExportRecordAsync(queueItem, Scripts));
else if (queueItem.Type == ImportRecordFlags.ServerConsoles)
manifest.ServerConsoles.Add(await ExportRecordAsync(queueItem, ServerConsoles));
else if (queueItem.Type == ImportRecordFlags.ServerHttpPaths)
await ExportRecordAsync(queueItem, ServerHttpPaths);
}
return manifest;
}
private async Task AddArchiveToExport(SDK.Models.Manifest.Archive archive, System.IO.Compression.ZipArchive zip)
{
try
{
var archivePath = await archiveService.GetArchiveFileLocationAsync(archive.ObjectKey);
if (Path.Exists(archivePath))
{
var archiveEntry = zip.CreateEntry($"Archives/{archive.Id}");
using (var archiveEntryStream = archiveEntry.Open())
using (var archiveFileStream = new FileStream(archivePath, FileMode.Open))
{
await archiveFileStream.CopyToAsync(archiveEntryStream);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Could not add archive {archive.Id} to export file");
}
}
private async Task AddMediaToExport(SDK.Models.Manifest.Media media, System.IO.Compression.ZipArchive zip)
{
try
{
var mediaPath = await mediaService.GetMediaPathAsync(media.Id);
if (Path.Exists(mediaPath))
{
var mediaEntry = zip.CreateEntry($"Media/{media.Id}");
using (var mediaEntryStream = mediaEntry.Open())
using (var mediaFileStream = new FileStream(mediaPath, FileMode.Open))
{
await mediaFileStream.CopyToAsync(mediaEntryStream);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Could not add media {media.Id} to export file");
}
}
private async Task AddSaveToExport(SDK.Models.Manifest.Save save, System.IO.Compression.ZipArchive zip)
{
try
{
var savePath = await saveService.GetSavePathAsync(save.Id);
if (Path.Exists(savePath))
{
var saveEntry = zip.CreateEntry($"Saves/{save.Id}");
using (var saveEntryStream = saveEntry.Open())
using (var saveFileStream = new FileStream(savePath, FileMode.Open))
{
await saveFileStream.CopyToAsync(saveEntryStream);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Could not add save {save.Id} to export file");
}
}
private async Task AddScriptToExport(SDK.Models.Manifest.Script script, System.IO.Compression.ZipArchive zip)
{
try
{
var scriptEntry = zip.CreateEntry($"Scripts/{script.Id}");
using (var ms = new MemoryStream())
using (var writer = new StreamWriter(ms))
{
await writer.WriteAsync(script.Contents);
await writer.FlushAsync();
await using (var entryStream = scriptEntry.Open())
{
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(entryStream);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Could not add script {script.Id} to export file");
}
}
private async IAsyncEnumerable<ImportItemInfo> GetImportItemInfoAsync<TModel, TEntity>(IEnumerable<TModel> records,
BaseImporter<TModel, TEntity> importer)
{
if (records != null)
foreach (var record in records)
{
if (record != null && record.GetType() == typeof(TModel))
yield return await importer.GetImportInfoAsync(record);
}
}
private async IAsyncEnumerable<ExportItemInfo> GetExportItemInfoAsync<TModel, TEntity>(IEnumerable<TEntity> records,
BaseImporter<TModel, TEntity> importer)
{
if (records != null)
foreach (var record in records)
{
if (record != null && record.GetType() == typeof(TEntity))
yield return await importer.GetExportInfoAsync(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);
}
}
private async Task<TRecord> ExportRecordAsync<TRecord, TEntity>(ImportQueueItem queueItem, BaseImporter<TRecord, TEntity> importer) where TEntity : BaseModel
{
var entity = queueItem.Record as TEntity;
try
{
var record = await importer.ExportAsync(entity.Id);
queueItem.Processed = true;
OnRecordProcessed?.Invoke(this, queueItem);
return record;
}
catch (Exception ex)
{
Errored.Add(queueItem, ex.Message);
OnRecordError?.Invoke(this, queueItem);
return default;
}
}
public void Dispose()
{
if (Archive != null)
Archive.Dispose();
}
}

View file

@ -1,6 +1,6 @@
using System.Net.Mime;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Importers;
using LANCommander.Server.ImportExport.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
@ -12,20 +12,20 @@ namespace LANCommander.Server.Controllers.Api;
[ApiController]
public class ExportController : BaseApiController
{
private readonly ImportService ImportService;
private readonly ExportService ExportService;
public ExportController(
ImportService importService,
ExportService exportService,
ILogger<ExportController> logger) : base(logger)
{
ImportService = importService;
ExportService = exportService;
}
//[Authorize(Roles = RoleService.AdministratorRoleName)]
[HttpGet("{contextId:guid}")]
public async Task ExportAsync(Guid contextId)
{
var context = ImportService.GetContext(contextId);
var context = ExportService.GetContext(contextId);
var syncIOFeature = HttpContext.Features.Get<IHttpBodyControlFeature>();
@ -35,6 +35,6 @@ public class ExportController : BaseApiController
Response.ContentType = MediaTypeNames.Application.Octet;
Response.Headers.Append("Content-Disposition", $"attachment; filename=\"export.lcx\"");
await ImportService.ExportAsync(contextId, Response.Body);
await ExportService.ExportAsync(contextId, Response.Body);
}
}

View file

@ -3,11 +3,11 @@ using LANCommander.SDK.Enums;
using LANCommander.Server.Data;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Extensions;
using LANCommander.Server.ImportExport;
using LANCommander.Server.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Abstractions;
using LANCommander.Server.Services.Enums;
using LANCommander.Server.Services.Importers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

View file

@ -1,9 +1,9 @@
using AutoMapper;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Extensions;
using LANCommander.Server.ImportExport;
using LANCommander.Server.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Importers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

View file

@ -1,9 +1,9 @@
using AutoMapper;
using LANCommander.Server.Data.Models;
using LANCommander.Server.Extensions;
using LANCommander.Server.ImportExport;
using LANCommander.Server.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Importers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

View file

@ -1,11 +1,7 @@
using LANCommander.SDK.Helpers;
using LANCommander.Server.Services;
using LANCommander.Server.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IO.Compression;
using LANCommander.SDK.Enums;
using LANCommander.Server.Services.Factories;
using LANCommander.Server.Services.Importers;
using LANCommander.Server.ImportExport.Factories;
namespace LANCommander.Server.Controllers
{
@ -13,26 +9,20 @@ namespace LANCommander.Server.Controllers
public class GamesController : BaseController
{
private readonly GameService GameService;
private readonly MediaService MediaService;
private readonly ArchiveService ArchiveService;
private readonly ImportContextFactory ImportContextFactory;
private readonly ExportContextFactory ExportContextFactory;
public GamesController(
ILogger<GamesController> logger,
GameService gameService,
MediaService mediaService,
ArchiveService archiveService,
ImportContextFactory importContextFactory) : base(logger)
ExportContextFactory exportContextFactory) : base(logger)
{
GameService = gameService;
MediaService = mediaService;
ArchiveService = archiveService;
ImportContextFactory = importContextFactory;
ExportContextFactory = exportContextFactory;
}
public async Task ExportAsync(Guid id, ImportRecordFlags flags)
{
using (var context = ImportContextFactory.Create())
using (var context = ExportContextFactory.Create())
{
var game = await GameService
.Include(g => g.Actions)

View file

@ -125,6 +125,7 @@
<ProjectReference Include="..\LANCommander.Server.Data.PostgreSQL\LANCommander.Server.Data.PostgreSQL.csproj" />
<ProjectReference Include="..\LANCommander.Server.Data.SQLite\LANCommander.Server.Data.SQLite.csproj" />
<ProjectReference Include="..\LANCommander.Server.Data\LANCommander.Server.Data.csproj" />
<ProjectReference Include="..\LANCommander.Server.ImportExport\LANCommander.Server.ImportExport.csproj" />
<ProjectReference Include="..\LANCommander.Server.Services\LANCommander.Server.Services.csproj" />
<ProjectReference Include="..\LANCommander.Steam\LANCommander.Steam.csproj" />
<ProjectReference Include="..\LANCommander.UI\LANCommander.UI.csproj" />

View file

@ -1,3 +1,4 @@
using LANCommander.Server.ImportExport.Extensions;
using LANCommander.Server.Providers;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Abstractions;
@ -15,6 +16,7 @@ public static class Services
Log.Debug("Registering services");
builder.Services.AddLANCommanderServer(settings);
builder.Services.AddLANCommanderImportExport();
builder.Services.AddLANCommanderUI();
builder.Services.AddSingleton<IVersionProvider, VersionProvider>();

View file

@ -1,9 +1,12 @@
@using LANCommander.SDK.Enums
@using LANCommander.Server.Services.Importers
@using LANCommander.Server.ImportExport
@using LANCommander.Server.ImportExport.Factories
@using LANCommander.Server.ImportExport.Models
@using LANCommander.Server.ImportExport.Services
@inherits FeedbackComponent<ExportDialogOptions>
@inject ArchiveService ArchiveService
@inject ImportService ImportService
@inject ImportContextFactory ImportContextFactory
@inject ExportService ExportService
@inject ExportContextFactory ExportContextFactory
@inject IMessageService MessageService
@inject NavigationManager NavigationManager
@inject ILogger<ExportDialog> Logger
@ -14,18 +17,20 @@
{
<Panel ShowArrow="false" Key="@group.Key.ToString()">
<HeaderTemplate>
<Checkbox @bind-Checked="SelectedFlags[group.Key]" />
<Flex Direction="FlexDirection.Horizontal" Justify="FlexJustify.SpaceBetween">
<Text>@group.Key</Text>
<Flex Direction="FlexDirection.Horizontal" Gap="FlexGap.Small">
<Checkbox @bind-Checked="SelectedFlags[group.Key]" />
<Flex Direction="FlexDirection.Horizontal" Justify="FlexJustify.SpaceBetween">
<Text>@group.Key (@(group.Count()))</Text>
@{
var size = group.Sum(i => i.Size);
@{
var size = group.Sum(i => i.Size);
if (size > 0)
{
<ByteSize Value="size" />
if (size > 0)
{
<ByteSize Value="size" />
}
}
}
</Flex>
</Flex>
</HeaderTemplate>
<ChildContent>
@ -55,11 +60,11 @@
</Flex>
@code {
ImportContext ImportContext;
ExportContext ExportContext;
IEnumerable<ExportItemInfo> ExportItems = new List<ExportItemInfo>();
Dictionary<ImportRecordFlags, bool> SelectedFlags = Enum.GetValues<ImportRecordFlags>()
Dictionary<ExportRecordFlags, bool> SelectedFlags = Enum.GetValues<ExportRecordFlags>()
.ToDictionary(
f => f,
_ => true);
@ -70,14 +75,14 @@
{
_loading = true;
ImportContext = ImportContextFactory.Create();
ExportContext = ExportContextFactory.Create();
ImportContext.OnRecordError += RecordError;
ImportContext.OnRecordAdded += RecordAdded;
ImportContext.OnRecordProcessed += RecordProcessed;
ExportContext.OnRecordError += RecordError;
ExportContext.OnRecordAdded += RecordAdded;
ExportContext.OnRecordProcessed += RecordProcessed;
if (Options != null && Options.RecordId != Guid.Empty)
ExportItems = await ImportContext.InitializeExportAsync(Options.RecordId, Options.RecordType);
ExportItems = await ExportContext.InitializeExportAsync(Options.RecordId, Options.RecordType);
_loading = false;
}
@ -101,13 +106,13 @@
{
var flags = SelectedFlags
.Where(f => f.Value)
.Aggregate(ImportRecordFlags.None, (cur, f) => cur | f.Key);
.Aggregate(ExportRecordFlags.None, (cur, f) => cur | f.Key);
await ImportContext.PrepareExportQueueAsync(flags);
await ExportContext.PrepareExportQueueAsync(flags);
await InvokeAsync(StateHasChanged);
var contextId = ImportService.EnqueueContext(ImportContext);
var contextId = ExportService.EnqueueContext(ExportContext);
NavigationManager.NavigateTo($"/api/Export/{contextId}", true);
}
@ -119,14 +124,14 @@
public override async Task OnFeedbackCancelAsync(ModalClosingEventArgs args)
{
//ImportContext.Dispose();
//ExportContext.Dispose();
await base.OnFeedbackCancelAsync(args);
}
public override async Task OnFeedbackOkAsync(ModalClosingEventArgs args)
{
//ImportContext.Dispose();
//ExportContext.Dispose();
await base.OnFeedbackOkAsync(args);
}
@ -139,7 +144,7 @@
{
var archivePath = await ArchiveService.GetArchiveFileLocationAsync(objectKey.ToString());
ExportItems = await ImportContext.InitializeExportAsync(Options.RecordId, Options.RecordType);
ExportItems = await ExportContext.InitializeExportAsync(Options.RecordId, Options.RecordType);
await InvokeAsync(StateHasChanged);
}

View file

@ -1,5 +1,8 @@
@using LANCommander.SDK.Enums
@using LANCommander.Server.Services.Importers
@using LANCommander.Server.ImportExport
@using LANCommander.Server.ImportExport.Factories
@using LANCommander.Server.ImportExport.Models
@using LANCommander.Server.ImportExport.Services
@inherits FeedbackComponent<ImportDialogOptions>
@inject GameService GameService
@inject ArchiveService ArchiveService
@ -49,20 +52,22 @@ else
<Collapse>
@foreach (var group in ImportItems.GroupBy(i => i.Flag))
{
<Panel ShowArrow="false" Key="@group.Key.ToString()">
<Panel Key="@group.Key.ToString()">
<HeaderTemplate>
<Checkbox @bind-Checked="SelectedFlags[group.Key]" />
<Flex Direction="FlexDirection.Horizontal" Justify="FlexJustify.SpaceBetween">
<Text>@group.Key</Text>
<Flex Direction="FlexDirection.Horizontal" Gap="FlexGap.Small">
<Checkbox @bind-Checked="SelectedFlags[group.Key]" />
<Flex Direction="FlexDirection.Horizontal" Justify="FlexJustify.SpaceBetween">
<Text>@group.Key (@(group.Count()))</Text>
@{
var size = group.Sum(i => i.Size);
@{
var size = group.Sum(i => i.Size);
if (size > 0)
{
<ByteSize Value="size" />
if (size > 0)
{
<ByteSize Value="size" />
}
}
}
</Flex>
</Flex>
</HeaderTemplate>
<ChildContent>

View file

@ -50,6 +50,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LANCommander.Tests", "LANCo
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LANCommander.Launcher", "LANCommander.Launcher", "{21913D6E-E036-41BD-A49E-19E65ADDE0DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LANCommander.Server.ImportExport", "LANCommander.Server.ImportExport\LANCommander.Server.ImportExport.csproj", "{8CFB23C4-4D93-4448-9F1E-575633AAE4AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -124,6 +126,10 @@ Global
{C6B4CEDF-761B-48FC-B6FE-5A66EF2412BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6B4CEDF-761B-48FC-B6FE-5A66EF2412BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6B4CEDF-761B-48FC-B6FE-5A66EF2412BE}.Release|Any CPU.Build.0 = Release|Any CPU
{8CFB23C4-4D93-4448-9F1E-575633AAE4AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CFB23C4-4D93-4448-9F1E-575633AAE4AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CFB23C4-4D93-4448-9F1E-575633AAE4AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CFB23C4-4D93-4448-9F1E-575633AAE4AD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -146,5 +152,6 @@ Global
{DEF4735D-DF0A-4CE7-931B-7CCE91191FF9} = {21913D6E-E036-41BD-A49E-19E65ADDE0DA}
{8719E821-2D43-4780-B13F-D4BD2EA17BFB} = {21913D6E-E036-41BD-A49E-19E65ADDE0DA}
{4F7C36A2-6F3F-440B-BEAE-148DA7DA6856} = {E760E41E-DED0-45DA-9443-AC77715AC6CA}
{8CFB23C4-4D93-4448-9F1E-575633AAE4AD} = {E760E41E-DED0-45DA-9443-AC77715AC6CA}
EndGlobalSection
EndGlobal