2026-01-24 23:35:39 -06:00
using System.IO.Compression ;
using LANCommander.Server.Data ;
2024-08-04 18:44:33 -05:00
using LANCommander.Server.Data.Models ;
2024-10-07 18:04:26 -05:00
using LANCommander.Server.Services.Extensions ;
2026-07-02 18:09:14 -05:00
using LANCommander.Server.Services.Mappers ;
2026-01-24 23:35:39 -06:00
using LANCommander.SDK.Enums ;
using LANCommander.SDK.Services ;
2025-02-09 18:53:40 -06:00
using Microsoft.AspNetCore.Http ;
2024-10-07 18:04:26 -05:00
using Microsoft.Extensions.Logging ;
2024-10-11 01:09:12 -05:00
using Microsoft.EntityFrameworkCore ;
2024-10-30 17:44:10 -05:00
using ZiggyCreatures.Caching.Fusion ;
2023-10-18 01:14:31 -05:00
2024-08-04 18:44:33 -05:00
namespace LANCommander.Server.Services
2023-10-18 01:14:31 -05:00
{
2025-02-01 02:21:34 -06:00
public sealed class RedistributableService (
2025-09-24 20:58:23 -05:00
ILogger < SDK . Services . RedistributableClient > logger ,
2026-01-24 23:35:39 -06:00
ArchiveService archiveService ,
2026-05-11 04:06:32 -05:00
StorageLocationService storageLocationService ,
2025-11-16 12:31:25 -06:00
SettingsProvider < Settings . Settings > settingsProvider ,
2026-01-24 23:35:39 -06:00
ScriptClient scriptClient ,
2025-02-01 02:21:34 -06:00
IFusionCache cache ,
2026-07-02 18:09:14 -05:00
SdkMapper sdkMapper ,
ManifestMapper manifestMapper ,
2025-02-09 18:53:40 -06:00
IHttpContextAccessor httpContextAccessor ,
2026-07-02 18:09:14 -05:00
IDbContextFactory < DatabaseContext > contextFactory ) : BaseDatabaseService < Redistributable > ( logger , settingsProvider , cache , httpContextAccessor , contextFactory )
2023-10-18 01:14:31 -05:00
{
2025-02-23 08:49:43 -06:00
public override async Task < Redistributable > AddAsync ( Redistributable entity )
{
await cache . ExpireGameCacheAsync ( ) ;
return await base . AddAsync ( entity , async context = >
{
await context . UpdateRelationshipAsync ( r = > r . Archives ) ;
await context . UpdateRelationshipAsync ( r = > r . Games ) ;
await context . UpdateRelationshipAsync ( r = > r . Pages ) ;
await context . UpdateRelationshipAsync ( r = > r . Scripts ) ;
} ) ;
}
2025-02-02 14:58:07 -06:00
public override async Task < Redistributable > UpdateAsync ( Redistributable entity )
2024-10-11 01:09:12 -05:00
{
2025-02-04 02:31:23 -06:00
if ( entity . Games ! = null & & entity . Games . Any ( ) )
{
foreach ( var game in entity . Games )
{
await cache . ExpireGameCacheAsync ( game ? . Id ) ;
}
}
2025-02-02 14:58:07 -06:00
return await base . UpdateAsync ( entity , async context = >
{
await context . UpdateRelationshipAsync ( r = > r . Archives ) ;
await context . UpdateRelationshipAsync ( r = > r . Games ) ;
await context . UpdateRelationshipAsync ( r = > r . Pages ) ;
await context . UpdateRelationshipAsync ( r = > r . Scripts ) ;
} ) ;
2024-10-11 01:09:12 -05:00
}
2025-04-11 02:31:29 -05:00
public async Task < SDK . Models . Manifest . Redistributable > GetManifestAsync ( Guid manifestId )
{
var redistributable = await AsNoTracking ( )
. AsSplitQuery ( )
. Query ( q = >
{
return q
. Include ( r = > r . Archives )
. Include ( r = > r . Scripts ) ;
} )
2025-10-05 19:41:24 -05:00
. GetAsync ( manifestId ) ;
2025-04-11 02:31:29 -05:00
2026-07-02 18:09:14 -05:00
return manifestMapper . ToManifest ( redistributable ) ;
2025-04-11 02:31:29 -05:00
}
2026-01-24 23:35:39 -06:00
2026-06-10 21:51:12 -05:00
public async Task < Archive > GetLatestArchiveAsync ( Guid id )
{
var redistributable = await AsNoTracking ( )
. AsSplitQuery ( )
. Include ( r = > r . Archives )
. GetAsync ( id ) ;
var latestArchive = redistributable . Archives . OrderByDescending ( a = > a . CreatedOn ) . FirstOrDefault ( ) ;
return latestArchive ;
}
public async Task < string > GetVersionAsync ( Guid id )
{
var latestArchive = await GetLatestArchiveAsync ( id ) ;
return latestArchive ? . Version ? ? String . Empty ;
}
public async Task < IEnumerable < Archive > > GetUpdatesAsync ( Guid redistributableId , string version )
{
var redistributable = await AsNoTracking ( )
. AsSplitQuery ( )
. Include ( r = > r . Archives )
. GetAsync ( redistributableId ) ;
if ( redistributable ? . Archives = = null | | ! redistributable . Archives . Any ( ) )
return [ ] ;
var orderedArchives = redistributable . Archives . OrderBy ( a = > a . CreatedOn ) . ToList ( ) ;
if ( string . IsNullOrWhiteSpace ( version ) )
return [ orderedArchives . Last ( ) ] ;
var installedArchive = orderedArchives . FirstOrDefault ( a = > a . Version = = version ) ;
if ( installedArchive = = null )
return [ orderedArchives . Last ( ) ] ;
var newerArchives = orderedArchives
. Where ( a = > a . CreatedOn > installedArchive . CreatedOn )
. ToList ( ) ;
return newerArchives ;
}
2026-01-24 23:35:39 -06:00
public async Task PackageAsync ( Guid id )
{
var redistributable = await AsNoTracking ( )
. AsSplitQuery ( )
. Include ( r = > r . Archives )
. Include ( r = > r . Scripts )
. Include ( r = > r . Games )
. GetAsync ( id ) ;
logger ? . LogInformation ( "Packaging redistributable {RedistributableName}" , redistributable . Name ) ;
var latestArchive = redistributable . Archives ? . OrderByDescending ( r = > r . CreatedOn ) . FirstOrDefault ( ) ;
2026-05-11 04:06:32 -05:00
var storageLocation = await storageLocationService . GetOrDefaultAsync ( latestArchive ? . StorageLocationId , StorageLocationType . Archive ) ;
2026-01-24 23:35:39 -06:00
2026-05-31 22:29:00 -05:00
string latestArchivePath = null ;
if ( latestArchive ! = null )
latestArchivePath = await archiveService . GetArchiveFileLocationAsync ( latestArchive ) ;
2026-01-24 23:35:39 -06:00
if ( redistributable . Scripts ? . Any ( s = > s . Type = = ScriptType . Package ) ? ? false )
{
foreach ( var script in redistributable . Scripts . Where ( s = > s . Type = = ScriptType . Package ) )
{
2026-07-02 18:09:14 -05:00
var package = await scriptClient . Redistributable_RunPackageScriptAsync ( sdkMapper . ToSdk ( script ) , sdkMapper . ToSdk ( redistributable ) , latestArchivePath ) ;
2026-01-24 23:35:39 -06:00
2026-05-10 12:14:32 -05:00
if ( package = = null )
{
2026-05-27 20:42:23 -05:00
var message = $"Could not package redistributable {redistributable.Name}, the package script did not return a result" ;
logger ? . LogError ( message ) ;
throw new Exception ( message ) ;
2026-05-10 12:14:32 -05:00
}
if ( String . IsNullOrWhiteSpace ( package . Path ) | | ! Directory . Exists ( package . Path ) )
2026-01-24 23:35:39 -06:00
{
2026-05-27 20:42:23 -05:00
var message = $"Could not package redistributable {redistributable.Name}, the path {package.Path} could not be found" ;
logger ? . LogError ( message ) ;
throw new Exception ( message ) ;
2026-01-24 23:35:39 -06:00
}
var archive = new Archive
{
Version = package . Version ,
RedistributableId = redistributable . Id ,
ObjectKey = Guid . NewGuid ( ) . ToString ( ) ,
LastVersion = latestArchive ,
2026-05-11 04:06:32 -05:00
StorageLocationId = storageLocation . Id ,
2026-01-24 23:35:39 -06:00
} ;
archive = await archiveService . AddAsync ( archive ) ;
var destination = await archiveService . GetArchiveFileLocationAsync ( archive ) ;
ZipFile . CreateFromDirectory ( package . Path , destination ) ;
2026-05-27 20:42:23 -05:00
await archiveService . RecalculateFileSizeArchiveAsync ( archive ) ;
logger ? . LogInformation ( "Successfully packaged {RedistributableName} and created new archive with version number {RedistributableVersion}" , redistributable . Name , archive . Version ) ;
2026-01-24 23:35:39 -06:00
}
}
else
{
2026-05-27 20:42:23 -05:00
var message = $"Could not package redistributable {redistributable.Name}, no packaging scripts are defined" ;
logger ? . LogWarning ( message ) ;
throw new Exception ( message ) ;
2026-01-24 23:35:39 -06:00
}
}
2023-10-18 01:14:31 -05:00
}
}