Add runtime platform flag to actions, scripts, and save paths

This commit is contained in:
Pat Hartl 2026-07-08 02:15:54 -05:00
parent 36bc769211
commit f31925e399
49 changed files with 30449 additions and 8 deletions

View file

@ -193,7 +193,8 @@ namespace LANCommander.SDK.Services
WorkingDirectory = a.WorkingDirectory,
IsPrimaryAction = a.IsPrimaryAction,
SortOrder = a.SortOrder,
Variables = a.Variables
Variables = a.Variables,
Platforms = a.Platforms
}));
}
}
@ -267,6 +268,11 @@ namespace LANCommander.SDK.Services
}
}
// Only surface actions that support the runtime the launcher is currently running on.
actions = actions
.Where(a => EnvironmentHelper.SupportsCurrentRuntime(a.Platforms))
.ToList();
return actions;
}

View file

@ -156,7 +156,7 @@ namespace LANCommander.SDK.Services
if (!Directory.Exists(Path.Combine(tempLocation, tempLocationFilePath)))
tempLocationFilePath = "Saves";
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == Enums.SavePathType.File))
foreach (var savePath in manifest.SavePaths.Where(sp => sp.Type == Enums.SavePathType.File && EnvironmentHelper.SupportsCurrentRuntime(sp.Platforms)))
{
var entries = GetFileSavePathEntries(savePath, installDirectory) ?? [];
@ -258,10 +258,10 @@ namespace LANCommander.SDK.Services
using (var savePacker = new SavePacker(installDirectory))
{
if (manifest?.SavePaths.Any() ?? false)
savePacker.AddPaths(manifest.SavePaths);
savePacker.AddPaths(manifest.SavePaths.Where(sp => EnvironmentHelper.SupportsCurrentRuntime(sp.Platforms)));
await savePacker.AddManifestAsync(manifest);
return await savePacker.PackAsync();
}
}
@ -272,7 +272,7 @@ namespace LANCommander.SDK.Services
.Create()
.UseAuthenticationToken()
.UseVersioning()
.UseRoute($"/api/Saves/Game/{manifest.Id}/Upload")
.UseRoute($"/api/Saves/Game/{manifest.Id}/Upload?platform={EnvironmentHelper.GetCurrentRuntime()}")
.UploadAsync<GameSave>($"game-{manifest.Id}-save", stream);
}
@ -283,7 +283,7 @@ namespace LANCommander.SDK.Services
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (manifest?.SavePaths?.Any() ?? false)
savePacker.AddPaths(manifest.SavePaths);
savePacker.AddPaths(manifest.SavePaths.Where(sp => EnvironmentHelper.SupportsCurrentRuntime(sp.Platforms)));
if (savePacker.HasEntries())
{

View file

@ -24,6 +24,12 @@ public partial class ScriptClient
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.Install);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.Install))
{
logger?.LogTrace("Skipping install script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing install script"))
{
if (File.Exists(path))
@ -92,6 +98,12 @@ public partial class ScriptClient
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.Uninstall);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.Uninstall))
{
logger?.LogTrace("Skipping uninstall script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing uninstall script"))
{
if (File.Exists(path))
@ -160,6 +172,12 @@ public partial class ScriptClient
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.BeforeStart);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.BeforeStart))
{
logger?.LogTrace("Skipping before start script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing before start script"))
{
if (File.Exists(path))
@ -231,6 +249,12 @@ public partial class ScriptClient
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.AfterStop);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.AfterStop))
{
logger?.LogTrace("Skipping after stop script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing after stop script"))
{
if (File.Exists(path))
@ -301,6 +325,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.NameChange);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.NameChange))
{
logger?.LogTrace("Skipping name change script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing name change script"))
{
if (File.Exists(path))
@ -385,6 +415,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.KeyChange);
var manifest = await ManifestHelper.ReadAsync<SDK.Models.Manifest.Game>(installDirectory, gameId);
if (File.Exists(path) && !SupportsCurrentRuntime(manifest.Scripts, Enums.ScriptType.KeyChange))
{
logger?.LogTrace("Skipping key change script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing key change script"))
{
if (File.Exists(path))

View file

@ -26,6 +26,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.DetectInstall);
if (File.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.DetectInstall))
{
logger?.LogTrace("Skipping detect install script; not supported on the current runtime");
return result;
}
try
{
if (File.Exists(path))
@ -112,7 +118,13 @@ public partial class ScriptClient
var redistributableManifest = await ManifestHelper.ReadAsync<Redistributable>(installDirectory, redistributableId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.Install);
if (Path.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.Install))
{
logger?.LogTrace("Skipping install script; not supported on the current runtime");
return result;
}
try
{
if (Path.Exists(path))
@ -186,6 +198,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.Uninstall);
if (Path.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.Uninstall))
{
logger?.LogTrace("Skipping uninstall script; not supported on the current runtime");
return result;
}
try
{
if (Path.Exists(path))
@ -261,6 +279,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.BeforeStart);
if (File.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.BeforeStart))
{
logger?.LogTrace("Skipping before start script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing before start script"))
{
if (File.Exists(path))
@ -341,6 +365,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.AfterStop);
if (File.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.AfterStop))
{
logger?.LogTrace("Skipping after stop script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing after stop script"))
{
if (File.Exists(path))
@ -421,6 +451,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.NameChange);
if (File.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.NameChange))
{
logger?.LogTrace("Skipping name change script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing name change script"))
{
if (File.Exists(path))
@ -511,6 +547,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, redistributableId, Enums.ScriptType.RunWrapper);
if (File.Exists(path) && !SupportsCurrentRuntime(redistributableManifest.Scripts, Enums.ScriptType.RunWrapper))
{
logger?.LogTrace("Skipping run wrapper script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing run wrapper script"))
{
if (File.Exists(path))

View file

@ -24,6 +24,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, toolId, Enums.ScriptType.DetectInstall);
if (File.Exists(path) && !SupportsCurrentRuntime(toolManifest.Scripts, Enums.ScriptType.DetectInstall))
{
logger?.LogTrace("Skipping detect install script; not supported on the current runtime");
return result;
}
try
{
if (File.Exists(path))
@ -109,7 +115,13 @@ public partial class ScriptClient
var toolManifest = await ManifestHelper.ReadAsync<Tool>(installDirectory, toolId);
var path = ScriptHelper.GetScriptFilePath(installDirectory, toolId, Enums.ScriptType.Install);
if (Path.Exists(path) && !SupportsCurrentRuntime(toolManifest.Scripts, Enums.ScriptType.Install))
{
logger?.LogTrace("Skipping install script; not supported on the current runtime");
return result;
}
try
{
if (Path.Exists(path))
@ -170,6 +182,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, toolId, Enums.ScriptType.Uninstall);
if (Path.Exists(path) && !SupportsCurrentRuntime(toolManifest.Scripts, Enums.ScriptType.Uninstall))
{
logger?.LogTrace("Skipping uninstall script; not supported on the current runtime");
return result;
}
try
{
if (Path.Exists(path))
@ -232,6 +250,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, toolId, Enums.ScriptType.BeforeStart);
if (File.Exists(path) && !SupportsCurrentRuntime(toolManifest.Scripts, Enums.ScriptType.BeforeStart))
{
logger?.LogTrace("Skipping before start script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing before start script"))
{
if (File.Exists(path))
@ -296,6 +320,12 @@ public partial class ScriptClient
var path = ScriptHelper.GetScriptFilePath(installDirectory, toolId, Enums.ScriptType.AfterStop);
if (File.Exists(path) && !SupportsCurrentRuntime(toolManifest.Scripts, Enums.ScriptType.AfterStop))
{
logger?.LogTrace("Skipping after stop script; not supported on the current runtime");
return result;
}
using (var op = logger.BeginOperation("Executing after stop script"))
{
if (File.Exists(path))

View file

@ -22,6 +22,20 @@ namespace LANCommander.SDK.Services
{
public bool Debug { get; set; }
private static bool SupportsCurrentRuntime(System.Collections.Generic.IEnumerable<SDK.Models.Manifest.Script> scripts, Enums.ScriptType type)
{
var platforms = scripts?.FirstOrDefault(s => s.Type == type)?.Platforms ?? Enums.RuntimePlatform.None;
return EnvironmentHelper.SupportsCurrentRuntime(platforms);
}
private static bool SupportsCurrentRuntime(System.Collections.Generic.IEnumerable<SDK.Models.Script> scripts, Enums.ScriptType type)
{
var platforms = scripts?.FirstOrDefault(s => s.Type == type)?.Platforms ?? Enums.RuntimePlatform.None;
return EnvironmentHelper.SupportsCurrentRuntime(platforms);
}
private async Task<bool> RunScriptExternallyAsync(PowerShellScript script)
{
var scriptRunners = serviceProvider.GetServices<IScriptInterceptor>();

View file

@ -0,0 +1,13 @@
using System;
namespace LANCommander.SDK.Enums
{
[Flags]
public enum RuntimePlatform
{
None = 0,
Windows = 1 << 0,
Linux = 1 << 1,
macOS = 1 << 2,
}
}

View file

@ -1,10 +1,38 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Helpers;
public static class EnvironmentHelper
{
/// <summary>
/// Returns the <see cref="RuntimePlatform"/> the application is currently executing on.
/// </summary>
public static RuntimePlatform GetCurrentRuntime()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return RuntimePlatform.Windows;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return RuntimePlatform.Linux;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return RuntimePlatform.macOS;
return RuntimePlatform.None;
}
/// <summary>
/// Determines whether the given set of platforms includes the current runtime. An unspecified
/// (<see cref="RuntimePlatform.None"/>) value is treated as "runs everywhere" for backwards compatibility.
/// </summary>
public static bool SupportsCurrentRuntime(RuntimePlatform platforms)
{
return platforms == RuntimePlatform.None || platforms.HasFlag(GetCurrentRuntime());
}
public static bool IsRunningInContainer()
{
if (File.Exists("/.dockerenv"))

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Models
{
@ -14,6 +15,7 @@ namespace LANCommander.SDK.Models
public bool IsPrimaryAction { get; set; }
public int SortOrder { get; set; }
public string OptionOverrides { get; set; }
public RuntimePlatform Platforms { get; set; }
public Dictionary<string, string> Variables { get; set; } = new Dictionary<string, string>();
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Models
{
@ -8,6 +9,7 @@ namespace LANCommander.SDK.Models
{
public Guid UserId { get; set; }
public long Size { get; set; }
public RuntimePlatform Platforms { get; set; }
public virtual User User { get; set; }
}
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using LANCommander.SDK.Enums;
namespace LANCommander.SDK.Models.Manifest
{
@ -11,6 +12,7 @@ namespace LANCommander.SDK.Models.Manifest
public bool IsPrimaryAction { get; set; }
public int SortOrder { get; set; }
public string OptionOverrides { get; set; }
public RuntimePlatform Platforms { get; set; }
public Dictionary<string, string> Variables { get; set; } = new Dictionary<string, string>();
}
}

View file

@ -11,6 +11,7 @@ namespace LANCommander.SDK.Models.Manifest
public string Path { get; set; }
public string WorkingDirectory { get; set; }
public bool IsRegex { get; set; }
public RuntimePlatform Platforms { get; set; }
public IEnumerable<SavePathEntry> Entries { get; set; }
}
}

View file

@ -10,5 +10,6 @@ namespace LANCommander.SDK.Models.Manifest
public string Name { get; set; }
public string Description { get; set; }
public bool RequiresAdmin { get; set; }
public RuntimePlatform Platforms { get; set; }
}
}

View file

@ -11,6 +11,7 @@ namespace LANCommander.SDK.Models
public string Path { get; set; }
public string WorkingDirectory { get; set; }
public bool IsRegex { get; set; }
public RuntimePlatform Platforms { get; set; }
public IEnumerable<SavePathEntry> Entries { get; set; }
}
}

View file

@ -11,6 +11,7 @@ namespace LANCommander.SDK.Models
public string Name { get; set; }
public string Description { get; set; }
public bool RequiresAdmin { get; set; }
public RuntimePlatform Platforms { get; set; }
public string Contents { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using LANCommander.SDK.Enums;
using LANCommander.SDK.Helpers;
using System.Management.Automation;
namespace LANCommander.SDK.PowerShell.Cmdlets
{
[Cmdlet(VerbsCommon.Get, "Runtime")]
[OutputType(typeof(RuntimePlatform))]
public class GetRuntimeCmdlet : Cmdlet
{
protected override void ProcessRecord()
{
WriteObject(EnvironmentHelper.GetCurrentRuntime());
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.MySQL.Migrations
{
/// <inheritdoc />
public partial class AddRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Scripts",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Actions",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "Scripts");
migrationBuilder.DropColumn(
name: "Platforms",
table: "Actions");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.MySQL.Migrations
{
/// <inheritdoc />
public partial class AddSavePathRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "SavePaths",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "SavePaths");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.MySQL.Migrations
{
/// <inheritdoc />
public partial class AddGameSavePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "GameSaves",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "GameSaves");
}
}
}

View file

@ -203,6 +203,9 @@ namespace LANCommander.Server.Data.MySQL.Migrations
b.Property<string>("Path")
.HasColumnType("longtext");
b.Property<int>("Platforms")
.HasColumnType("int");
b.Property<bool>("PrimaryAction")
.HasColumnType("tinyint(1)");
@ -692,6 +695,9 @@ namespace LANCommander.Server.Data.MySQL.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("char(36)");
b.Property<int>("Platforms")
.HasColumnType("int");
b.Property<long>("Size")
.HasColumnType("bigint");
@ -1324,6 +1330,9 @@ namespace LANCommander.Server.Data.MySQL.Migrations
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Platforms")
.HasColumnType("int");
b.Property<int>("Type")
.HasColumnType("int");
@ -1373,6 +1382,9 @@ namespace LANCommander.Server.Data.MySQL.Migrations
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Platforms")
.HasColumnType("int");
b.Property<Guid?>("RedistributableId")
.HasColumnType("char(36)");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.PostgreSQL.Migrations
{
/// <inheritdoc />
public partial class AddRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Scripts",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Actions",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "Scripts");
migrationBuilder.DropColumn(
name: "Platforms",
table: "Actions");
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.PostgreSQL.Migrations
{
/// <inheritdoc />
public partial class AddSavePathRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "SavePaths",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "SavePaths");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Server.Data.PostgreSQL.Migrations
{
/// <inheritdoc />
public partial class AddGameSavePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "GameSaves",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "GameSaves");
}
}
}

View file

@ -203,6 +203,9 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
b.Property<string>("Path")
.HasColumnType("text");
b.Property<int>("Platforms")
.HasColumnType("integer");
b.Property<bool>("PrimaryAction")
.HasColumnType("boolean");
@ -692,6 +695,9 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("uuid");
b.Property<int>("Platforms")
.HasColumnType("integer");
b.Property<long>("Size")
.HasColumnType("bigint");
@ -1324,6 +1330,9 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<int>("Platforms")
.HasColumnType("integer");
b.Property<int>("Type")
.HasColumnType("integer");
@ -1373,6 +1382,9 @@ namespace LANCommander.Server.Data.PostgreSQL.Migrations
.IsRequired()
.HasColumnType("text");
b.Property<int>("Platforms")
.HasColumnType("integer");
b.Property<Guid?>("RedistributableId")
.HasColumnType("uuid");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Scripts",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "Actions",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "Scripts");
migrationBuilder.DropColumn(
name: "Platforms",
table: "Actions");
}
}
}

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddSavePathRuntimePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "SavePaths",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "SavePaths");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class AddGameSavePlatforms : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Platforms",
table: "GameSaves",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Platforms",
table: "GameSaves");
}
}
}

View file

@ -198,6 +198,9 @@ namespace LANCommander.Migrations
b.Property<string>("Path")
.HasColumnType("TEXT");
b.Property<int>("Platforms")
.HasColumnType("INTEGER");
b.Property<bool>("PrimaryAction")
.HasColumnType("INTEGER");
@ -687,6 +690,9 @@ namespace LANCommander.Migrations
b.Property<Guid?>("GameId")
.HasColumnType("TEXT");
b.Property<int>("Platforms")
.HasColumnType("INTEGER");
b.Property<long>("Size")
.HasColumnType("INTEGER");
@ -1317,6 +1323,9 @@ namespace LANCommander.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Platforms")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
@ -1366,6 +1375,9 @@ namespace LANCommander.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Platforms")
.HasColumnType("INTEGER");
b.Property<Guid?>("RedistributableId")
.HasColumnType("TEXT");

View file

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Data.Models
{
@ -13,6 +14,7 @@ namespace LANCommander.Server.Data.Models
public bool PrimaryAction { get; set; }
public int SortOrder { get; set; }
public string? OptionOverrides { get; set; }
public RuntimePlatform Platforms { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]

View file

@ -1,11 +1,14 @@
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using LANCommander.SDK.Enums;
namespace LANCommander.Server.Data.Models
{
[Table("GameSaves")]
public class GameSave : BaseModel
{
public RuntimePlatform Platforms { get; set; }
public Guid StorageLocationId { get; set; }
[JsonIgnore]
[ForeignKey(nameof(StorageLocationId))]

View file

@ -11,6 +11,7 @@ namespace LANCommander.Server.Data.Models
public string Path { get; set; }
public string? WorkingDirectory { get; set; }
public bool IsRegex { get; set; }
public RuntimePlatform Platforms { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]

View file

@ -14,6 +14,7 @@ namespace LANCommander.Server.Data.Models
public ScriptType Type { get; set; }
public string Contents { get; set; }
public bool RequiresAdmin { get; set; }
public RuntimePlatform Platforms { get; set; }
public Guid? GameId { get; set; }
[JsonIgnore]

View file

@ -41,6 +41,7 @@ public class ActionImporter(
Path = record.Path,
WorkingDirectory = record.WorkingDirectory,
PrimaryAction = record.IsPrimaryAction,
Platforms = record.Platforms,
SortOrder = record.SortOrder,
OptionOverrides = record.OptionOverrides,
CreatedOn = record.CreatedOn,
@ -89,6 +90,7 @@ public class ActionImporter(
existing.Path = record.Path;
existing.WorkingDirectory = record.WorkingDirectory;
existing.PrimaryAction = record.IsPrimaryAction;
existing.Platforms = record.Platforms;
existing.SortOrder = record.SortOrder;
existing.OptionOverrides = record.OptionOverrides;
existing.CreatedOn = record.CreatedOn;

View file

@ -43,6 +43,7 @@ public class SavePathImporter(
Path = record.Path,
WorkingDirectory = record.WorkingDirectory,
IsRegex = record.IsRegex,
Platforms = record.Platforms,
Type = record.Type,
CreatedOn = record.CreatedOn,
UpdatedOn = record.UpdatedOn,
@ -71,6 +72,7 @@ public class SavePathImporter(
existing.Path = record.Path;
existing.WorkingDirectory = record.WorkingDirectory;
existing.IsRegex = record.IsRegex;
existing.Platforms = record.Platforms;
existing.Type = record.Type;
existing.Game = await gameService.GetAsync(game.Id);

View file

@ -65,6 +65,7 @@ public class ScriptImporter(
Name = record.Name,
Description = record.Description,
RequiresAdmin = record.RequiresAdmin,
Platforms = record.Platforms,
Type = record.Type,
Contents = string.Empty,
};
@ -168,6 +169,7 @@ public class ScriptImporter(
existing.Name = record.Name;
existing.Description = record.Description;
existing.RequiresAdmin = record.RequiresAdmin;
existing.Platforms = record.Platforms;
existing.Type = record.Type;
await scriptService.UpdateAsync(existing);

View file

@ -213,6 +213,7 @@ public static class SaveEndpoints
public static async Task<IResult> UploadSaveByGameAsync(
Guid gameId,
[FromQuery] RuntimePlatform platform,
ClaimsPrincipal userPrincipal,
HttpContext httpContext,
[FromServices] ILogger<Program> logger,
@ -286,6 +287,7 @@ public static class SaveEndpoints
Game = game,
User = user,
Size = stream.Length,
Platforms = platform,
StorageLocation = saveStorageLocation,
};

View file

@ -26,6 +26,9 @@
<Button Icon="@IconType.Outline.Edit" Type="ButtonType.Text" OnClick="() => OpenVariablePicker(context, nameof(Action.WorkingDirectory))" Size="ButtonSize.Small" />
</Flex>
</PropertyColumn>
<PropertyColumn Property="a => a.Platforms" Title="Platforms">
<RuntimePlatformSelect @bind-Value="context.Platforms" />
</PropertyColumn>
<PropertyColumn Property="a => a.PrimaryAction" Title="Primary" Style="text-align: center">
<Checkbox @bind-Checked="context.PrimaryAction"/>
</PropertyColumn>

View file

@ -0,0 +1,47 @@
@using LANCommander.SDK.Enums
<Select TItem="PlatformOption"
TItemValue="RuntimePlatform"
Mode="SelectMode.Multiple"
AllowClear
DataSource="@Options"
Values="@SelectedFlags"
ValuesChanged="OnValuesChanged"
LabelName="@nameof(PlatformOption.Label)"
ValueName="@nameof(PlatformOption.Value)"
Placeholder="@Placeholder"
Style="@Style" />
@code {
[Parameter] public RuntimePlatform Value { get; set; }
[Parameter] public EventCallback<RuntimePlatform> ValueChanged { get; set; }
[Parameter] public string Placeholder { get; set; } = "All platforms";
[Parameter] public string Style { get; set; } = "min-width: 180px";
record PlatformOption(RuntimePlatform Value, string Label);
static readonly PlatformOption[] Options =
{
new(RuntimePlatform.Windows, "Windows"),
new(RuntimePlatform.Linux, "Linux"),
new(RuntimePlatform.macOS, "macOS"),
};
IEnumerable<RuntimePlatform> SelectedFlags =>
Options.Where(o => Value.HasFlag(o.Value)).Select(o => o.Value);
async Task OnValuesChanged(IEnumerable<RuntimePlatform> values)
{
var combined = RuntimePlatform.None;
if (values != null)
{
foreach (var value in values)
combined |= value;
}
Value = combined;
await ValueChanged.InvokeAsync(combined);
}
}

View file

@ -104,6 +104,10 @@
<FormItem Label="Description">
<TextArea @bind-Value="context.Description" MaxLength=500 ShowCount />
</FormItem>
<FormItem Label="Platforms">
<RuntimePlatformSelect @bind-Value="context.Platforms" />
</FormItem>
</Form>

View file

@ -34,6 +34,9 @@
<PropertyColumn Property="p => p.IsRegex" Title="Regex">
<Checkbox @bind-Checked="context.IsRegex" Disabled="@(context.Type == SavePathType.Registry)" />
</PropertyColumn>
<PropertyColumn Property="p => p.Platforms" Title="Platforms">
<RuntimePlatformSelect @bind-Value="context.Platforms" />
</PropertyColumn>
<ActionColumn>
<Flex Gap="FlexGap.Small" Justify="FlexJustify.End">
<Button OnClick="() => RemovePath(context)" Type="@ButtonType.Text" Danger Icon="@IconType.Outline.Close" />