Merge branch 'redistributables'
commit
ff9ec5a17b
|
@ -50,7 +50,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
var result = RetryHelper.RetryOnException<ExtractionResult>(10, TimeSpan.FromMilliseconds(500), new ExtractionResult(), () =>
|
||||
{
|
||||
Logger.Trace("Attempting to download and extract game...");
|
||||
return DownloadAndExtract(game);
|
||||
return DownloadAndExtractGame(game);
|
||||
});
|
||||
|
||||
if (!result.Success && !result.Canceled)
|
||||
|
@ -88,6 +88,12 @@ namespace LANCommander.PlaynitePlugin
|
|||
SaveScript(game, result.Directory, ScriptType.NameChange);
|
||||
SaveScript(game, result.Directory, ScriptType.KeyChange);
|
||||
|
||||
if (game.Redistributables != null && game.Redistributables.Count() > 0)
|
||||
{
|
||||
Logger.Trace("Installing required redistributables...");
|
||||
InstallRedistributables(game);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
PowerShellRuntime.RunScript(PlayniteGame, ScriptType.Install);
|
||||
|
@ -106,7 +112,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtract(LANCommander.SDK.Models.Game game)
|
||||
private ExtractionResult DownloadAndExtractGame(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
if (game == null)
|
||||
{
|
||||
|
@ -204,6 +210,153 @@ namespace LANCommander.PlaynitePlugin
|
|||
return extractionResult;
|
||||
}
|
||||
|
||||
private void InstallRedistributables(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
foreach (var redistributable in game.Redistributables)
|
||||
{
|
||||
string installScriptTempFile = null;
|
||||
string detectionScriptTempFile = null;
|
||||
string extractTempPath = null;
|
||||
|
||||
try
|
||||
{
|
||||
var installScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.Install);
|
||||
installScriptTempFile = SaveTempScript(installScript);
|
||||
|
||||
var detectionScript = redistributable.Scripts.FirstOrDefault(s => s.Type == ScriptType.DetectInstall);
|
||||
detectionScriptTempFile = SaveTempScript(detectionScript);
|
||||
|
||||
var detectionResult = PowerShellRuntime.RunScript(detectionScriptTempFile, detectionScript.RequiresAdmin);
|
||||
|
||||
// Redistributable is not installed
|
||||
if (detectionResult == 0)
|
||||
{
|
||||
var extractionResult = DownloadAndExtractRedistributable(redistributable);
|
||||
|
||||
if (extractionResult.Success)
|
||||
{
|
||||
extractTempPath = extractionResult.Directory;
|
||||
|
||||
PowerShellRuntime.RunScript(installScriptTempFile, installScript.RequiresAdmin, null, extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Redistributable {redistributable.Name} failed to install");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(installScriptTempFile))
|
||||
File.Delete(installScriptTempFile);
|
||||
|
||||
if (File.Exists(detectionScriptTempFile))
|
||||
File.Delete(detectionScriptTempFile);
|
||||
|
||||
if (Directory.Exists(extractTempPath))
|
||||
Directory.Delete(extractTempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ExtractionResult DownloadAndExtractRedistributable(LANCommander.SDK.Models.Redistributable redistributable)
|
||||
{
|
||||
if (redistributable == null)
|
||||
{
|
||||
Logger.Trace("Redistributable failed to download! No redistributable was specified!");
|
||||
|
||||
throw new Exception("Redistributable failed to download!");
|
||||
}
|
||||
|
||||
var destination = Path.Combine(Path.GetTempPath(), redistributable.Name.SanitizeFilename());
|
||||
|
||||
Logger.Trace($"Downloading and extracting \"{redistributable.Name}\" to path {destination}");
|
||||
var result = Plugin.PlayniteApi.Dialogs.ActivateGlobalProgress(progress =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destination);
|
||||
progress.ProgressMaxValue = 100;
|
||||
progress.CurrentProgressValue = 0;
|
||||
|
||||
using (var redistributableStream = Plugin.LANCommander.StreamRedistributable(redistributable.Id))
|
||||
using (var reader = ReaderFactory.Open(redistributableStream))
|
||||
{
|
||||
progress.ProgressMaxValue = redistributableStream.Length;
|
||||
|
||||
redistributableStream.OnProgress += (pos, len) =>
|
||||
{
|
||||
progress.CurrentProgressValue = pos;
|
||||
};
|
||||
|
||||
reader.EntryExtractionProgress += (object sender, ReaderExtractionEventArgs<IEntry> e) =>
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
reader.Cancel();
|
||||
progress.IsIndeterminate = true;
|
||||
|
||||
reader.Dispose();
|
||||
redistributableStream.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
reader.WriteAllToDirectory(destination, new ExtractionOptions()
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (progress.CancelToken != null && progress.CancelToken.IsCancellationRequested)
|
||||
{
|
||||
Logger.Trace("User cancelled the download");
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after cancelled install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(ex, $"Could not extract to path {destination}");
|
||||
|
||||
if (Directory.Exists(destination))
|
||||
{
|
||||
Logger.Trace("Cleaning up orphaned install files after bad install...");
|
||||
|
||||
Directory.Delete(destination, true);
|
||||
}
|
||||
|
||||
throw new Exception("The redistributable archive could not be extracted. Please try again or fix the archive!");
|
||||
}
|
||||
}
|
||||
},
|
||||
new GlobalProgressOptions($"Downloading {redistributable.Name}...")
|
||||
{
|
||||
IsIndeterminate = false,
|
||||
Cancelable = true,
|
||||
});
|
||||
|
||||
var extractionResult = new ExtractionResult
|
||||
{
|
||||
Canceled = result.Canceled
|
||||
};
|
||||
|
||||
if (!result.Canceled)
|
||||
{
|
||||
extractionResult.Success = true;
|
||||
extractionResult.Directory = destination;
|
||||
Logger.Trace($"Redistributable successfully downloaded and extracted to {destination}");
|
||||
}
|
||||
|
||||
return extractionResult;
|
||||
}
|
||||
|
||||
private string Download(LANCommander.SDK.Models.Game game)
|
||||
{
|
||||
string tempFile = String.Empty;
|
||||
|
@ -294,6 +447,21 @@ namespace LANCommander.PlaynitePlugin
|
|||
File.WriteAllText(destination, yaml);
|
||||
}
|
||||
|
||||
private string SaveTempScript(LANCommander.SDK.Models.Script script)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
|
||||
File.Move(tempPath, tempPath + ".ps1");
|
||||
|
||||
tempPath = tempPath + ".ps1";
|
||||
|
||||
Logger.Trace($"Writing script {script.Name} to {tempPath}");
|
||||
|
||||
File.WriteAllText(tempPath, script.Contents);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
|
||||
{
|
||||
var script = game.Scripts.FirstOrDefault(s => s.Type == type);
|
||||
|
|
|
@ -206,6 +206,11 @@ namespace LANCommander.PlaynitePlugin
|
|||
return DownloadRequest($"/api/Archives/Download/{id}", progressHandler, completeHandler);
|
||||
}
|
||||
|
||||
public TrackableStream StreamRedistributable(Guid id)
|
||||
{
|
||||
return StreamRequest($"/api/Redistributables/{id}/Download");
|
||||
}
|
||||
|
||||
public string DownloadSave(Guid id, Action<DownloadProgressChangedEventArgs> progressHandler, Action<AsyncCompletedEventArgs> completeHandler)
|
||||
{
|
||||
return DownloadRequest($"/api/Saves/Download/{id}", progressHandler, completeHandler);
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
File.Delete(tempScript);
|
||||
}
|
||||
|
||||
public void RunScript(string path, bool asAdmin = false, string arguments = null)
|
||||
public int RunScript(string path, bool asAdmin = false, string arguments = null, string workingDirectory = null)
|
||||
{
|
||||
Logger.Trace($"Executing script at path {path} | Admin: {asAdmin} | Arguments: {arguments}");
|
||||
|
||||
|
@ -58,6 +58,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
if (arguments != null)
|
||||
process.StartInfo.Arguments += " " + arguments;
|
||||
|
||||
if (workingDirectory != null)
|
||||
process.StartInfo.WorkingDirectory = workingDirectory;
|
||||
|
||||
if (asAdmin)
|
||||
{
|
||||
process.StartInfo.Verb = "runas";
|
||||
|
@ -68,6 +71,8 @@ namespace LANCommander.PlaynitePlugin
|
|||
process.WaitForExit();
|
||||
|
||||
Wow64RevertWow64FsRedirection(ref wow64Value);
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
|
||||
public void RunScript(Game game, ScriptType type, string arguments = null)
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
Install,
|
||||
Uninstall,
|
||||
NameChange,
|
||||
KeyChange
|
||||
KeyChange,
|
||||
SaveUpload,
|
||||
SaveDownload,
|
||||
DetectInstall
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,5 +16,6 @@ namespace LANCommander.SDK.Models
|
|||
public virtual Company Developer { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
public virtual IEnumerable<Redistributable> Redistributables { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LANCommander.SDK.Models
|
||||
{
|
||||
public class Redistributable : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public DateTime ReleasedOn { get; set; }
|
||||
public virtual IEnumerable<Archive> Archives { get; set; }
|
||||
public virtual IEnumerable<Script> Scripts { get; set; }
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<Space Direction="DirectionVHType.Vertical" Style="width: 100%">
|
||||
<SpaceItem>
|
||||
<Table TItem="Archive" DataSource="@Game.Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true" Responsive>
|
||||
<Table TItem="Archive" DataSource="@Archives.OrderByDescending(a => a.CreatedOn)" HidePagination="true" Responsive>
|
||||
<PropertyColumn Property="a => a.Version" />
|
||||
<PropertyColumn Property="a => a.CompressedSize">
|
||||
@ByteSizeLib.ByteSize.FromBytes(context.CompressedSize)
|
||||
|
@ -23,7 +23,7 @@
|
|||
<ActionColumn Title="">
|
||||
<Space Style="display: flex; justify-content: end">
|
||||
<SpaceItem>
|
||||
<a href="/Download/Game/@context.Id" target="_blank" class="ant-btn ant-btn-text ant-btn-icon-only">
|
||||
<a href="/Download/Archive/@context.Id" target="_blank" class="ant-btn ant-btn-text ant-btn-icon-only">
|
||||
<Icon Type="@IconType.Outline.Download" />
|
||||
</a>
|
||||
</SpaceItem>
|
||||
|
@ -49,7 +49,10 @@
|
|||
<ArchiveUploader @ref="Uploader" OnArchiveUploaded="AddArchive" />
|
||||
|
||||
@code {
|
||||
[Parameter] public Game Game { get; set; }
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
[Parameter] public ICollection<Archive> Archives { get; set; }
|
||||
[Parameter] public EventCallback<ICollection<Archive>> ArchivesChanged { get; set; }
|
||||
|
||||
Archive Archive;
|
||||
ArchiveUploader Uploader;
|
||||
|
@ -63,10 +66,7 @@
|
|||
|
||||
private async Task LoadData()
|
||||
{
|
||||
Game.Archives = await ArchiveService.Get(a => a.GameId == Game.Id).OrderByDescending(a => a.CreatedOn).ToListAsync();
|
||||
|
||||
if (Game.Archives == null)
|
||||
Game.Archives = new List<Archive>();
|
||||
Archives = await ArchiveService.Get(a => a.GameId == GameId).OrderByDescending(a => a.CreatedOn).ToListAsync();
|
||||
}
|
||||
|
||||
private async Task Download(Archive archive)
|
||||
|
@ -78,18 +78,18 @@
|
|||
|
||||
private async Task UploadArchive()
|
||||
{
|
||||
Archive = new Archive()
|
||||
{
|
||||
GameId = Game.Id,
|
||||
Id = Guid.NewGuid()
|
||||
};
|
||||
if (GameId != Guid.Empty)
|
||||
Archive = new Archive() { GameId = GameId, Id = Guid.NewGuid() };
|
||||
|
||||
if (RedistributableId != Guid.Empty)
|
||||
Archive = new Archive() { RedistributableId = RedistributableId, Id = Guid.NewGuid() };
|
||||
|
||||
await Uploader.Open(Archive);
|
||||
}
|
||||
|
||||
private async Task AddArchive(Archive archive)
|
||||
{
|
||||
var lastArchive = Game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
|
||||
var lastArchive = Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
|
||||
|
||||
Archive = await ArchiveService.Add(archive);
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
</FormItem>
|
||||
|
||||
<FormItem Label="Type">
|
||||
<Select @bind-Value="context.Type" TItem="ScriptType" TItemValue="ScriptType" DataSource="Enum.GetValues<ScriptType>()">
|
||||
<Select @bind-Value="context.Type" TItem="ScriptType" TItemValue="ScriptType" DataSource="Enum.GetValues<ScriptType>().Where(st => AllowedTypes == null || AllowedTypes.Contains(st))">
|
||||
<LabelTemplate Context="Value">@Value.GetDisplayName()</LabelTemplate>
|
||||
<ItemTemplate Context="Value">@Value.GetDisplayName()</ItemTemplate>
|
||||
</Select>
|
||||
|
@ -111,11 +111,13 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
@code {
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
[Parameter] public Guid RedistributableId { get; set; }
|
||||
[Parameter] public Guid ArchiveId { get; set; }
|
||||
[Parameter] public ICollection<Script> Scripts { get; set; }
|
||||
[Parameter] public EventCallback<ICollection<Script>> ScriptsChanged { get; set; }
|
||||
[Parameter] public IEnumerable<ScriptType> AllowedTypes { get; set; }
|
||||
|
||||
Script Script;
|
||||
|
||||
|
@ -150,10 +152,11 @@
|
|||
private async void Edit(Script script = null)
|
||||
{
|
||||
if (script == null) {
|
||||
Script = new Script()
|
||||
{
|
||||
GameId = GameId
|
||||
};
|
||||
if (GameId != Guid.Empty)
|
||||
Script = new Script() { GameId = GameId };
|
||||
|
||||
if (RedistributableId != Guid.Empty)
|
||||
Script = new Script() { RedistributableId = RedistributableId };
|
||||
|
||||
if (Editor != null)
|
||||
await Editor.SetValue("");
|
|
@ -0,0 +1,37 @@
|
|||
@typeparam TItem where TItem : BaseModel
|
||||
|
||||
<Transfer DataSource="TransferItems" TargetKeys="TargetKeys" OnChange="OnChange" Titles="new string[] { LeftTitle, RightTitle }" />
|
||||
|
||||
@code {
|
||||
[Parameter] public string LeftTitle { get; set; } = "";
|
||||
[Parameter] public string RightTitle { get; set; } = "";
|
||||
[Parameter] public Func<TItem, string> TitleSelector { get; set; }
|
||||
[Parameter] public IEnumerable<TItem> DataSource { get; set; }
|
||||
[Parameter] public ICollection<TItem> Values { get; set; } = new List<TItem>();
|
||||
[Parameter] public EventCallback<ICollection<TItem>> ValuesChanged { get; set; }
|
||||
|
||||
IEnumerable<TransferItem> TransferItems { get; set; } = new List<TransferItem>();
|
||||
List<string> TargetKeys { get; set; } = new List<string>();
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
TransferItems = DataSource.Select(i => new TransferItem()
|
||||
{
|
||||
Key = i.Id.ToString(),
|
||||
Title = TitleSelector.Invoke(i)
|
||||
});
|
||||
|
||||
TargetKeys = Values.Select(i => i.Id.ToString()).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnChange(TransferChangeArgs e)
|
||||
{
|
||||
Values = DataSource.Where(i => e.TargetKeys.Contains(i.Id.ToString())).ToList();
|
||||
|
||||
if (ValuesChanged.HasDelegate)
|
||||
await ValuesChanged.InvokeAsync(Values);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using LANCommander.Data.Models;
|
||||
using LANCommander.Extensions;
|
||||
using LANCommander.Models;
|
||||
using LANCommander.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LANCommander.Controllers.Api
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class RedistributableController : ControllerBase
|
||||
{
|
||||
private readonly RedistributableService RedistributableService;
|
||||
private readonly LANCommanderSettings Settings = SettingService.GetSettings();
|
||||
|
||||
public RedistributableController(RedistributableService redistributableService)
|
||||
{
|
||||
|
||||
RedistributableService = redistributableService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Redistributable>> Get()
|
||||
{
|
||||
return await RedistributableService.Get();
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Redistributable> Get(Guid id)
|
||||
{
|
||||
return await RedistributableService.Get(id);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/Download")]
|
||||
public async Task<IActionResult> Download(Guid id)
|
||||
{
|
||||
var redistributable = await RedistributableService.Get(id);
|
||||
|
||||
if (redistributable == null)
|
||||
return NotFound();
|
||||
|
||||
if (redistributable.Archives == null || redistributable.Archives.Count == 0)
|
||||
return NotFound();
|
||||
|
||||
var archive = redistributable.Archives.OrderByDescending(a => a.CreatedOn).First();
|
||||
|
||||
var filename = Path.Combine(Settings.Archives.StoragePath, archive.ObjectKey);
|
||||
|
||||
if (!System.IO.File.Exists(filename))
|
||||
return NotFound();
|
||||
|
||||
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{redistributable.Name.SanitizeFilename()}.zip");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ namespace LANCommander.Controllers
|
|||
ArchiveService = archiveService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Game(Guid id)
|
||||
public async Task<IActionResult> Archive(Guid id)
|
||||
{
|
||||
var archive = await ArchiveService.Get(id);
|
||||
|
||||
|
|
|
@ -37,13 +37,13 @@ namespace LANCommander.Data
|
|||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Archives)
|
||||
.WithOne(g => g.Game)
|
||||
.IsRequired(true)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Scripts)
|
||||
.WithOne(s => s.Game)
|
||||
.IsRequired(true)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Game>()
|
||||
|
@ -81,6 +81,15 @@ namespace LANCommander.Data
|
|||
g => g.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||
);
|
||||
|
||||
builder.Entity<Game>()
|
||||
.HasMany(g => g.Redistributables)
|
||||
.WithMany(r => r.Games)
|
||||
.UsingEntity<Dictionary<string, object>>(
|
||||
"GameRedistributable",
|
||||
gr => gr.HasOne<Redistributable>().WithMany().HasForeignKey("RedistributableId"),
|
||||
gr => gr.HasOne<Game>().WithMany().HasForeignKey("GameId")
|
||||
);
|
||||
|
||||
builder.Entity<User>()
|
||||
.HasMany(u => u.GameSaves)
|
||||
.WithOne(gs => gs.User)
|
||||
|
@ -104,6 +113,18 @@ namespace LANCommander.Data
|
|||
.WithOne(sl => sl.Server)
|
||||
.IsRequired(true)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Redistributable>()
|
||||
.HasMany(r => r.Archives)
|
||||
.WithOne(a => a.Redistributable)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
builder.Entity<Redistributable>()
|
||||
.HasMany(r => r.Scripts)
|
||||
.WithOne(s => s.Redistributable)
|
||||
.IsRequired(false)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
public DbSet<Game>? Games { get; set; }
|
||||
|
@ -123,5 +144,7 @@ namespace LANCommander.Data
|
|||
public DbSet<Server>? Servers { get; set; }
|
||||
|
||||
public DbSet<ServerConsole>? ServerConsoles { get; set; }
|
||||
|
||||
public DbSet<Redistributable>? Redistributables { get; set; }
|
||||
}
|
||||
}
|
|
@ -13,6 +13,8 @@ namespace LANCommander.Data.Enums
|
|||
[Display(Name = "Save Upload")]
|
||||
SaveUpload,
|
||||
[Display(Name = "Save Download")]
|
||||
SaveDownload
|
||||
SaveDownload,
|
||||
[Display(Name = "Detect Install")]
|
||||
DetectInstall
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,12 +13,18 @@ namespace LANCommander.Data.Models
|
|||
[Required]
|
||||
public string Version { get; set; }
|
||||
|
||||
public Guid GameId { get; set; }
|
||||
public Guid? GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("Archives")]
|
||||
public virtual Game? Game { get; set; }
|
||||
|
||||
public Guid? RedistributableId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(RedistributableId))]
|
||||
[InverseProperty("Archives")]
|
||||
public virtual Redistributable? Redistributable { get; set; }
|
||||
|
||||
[Display(Name = "Last Version")]
|
||||
public virtual Archive? LastVersion { get; set; }
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace LANCommander.Data.Models
|
|||
public virtual ICollection<GameSave>? GameSaves { get; set; }
|
||||
public virtual ICollection<SavePath>? SavePaths { get; set; }
|
||||
public virtual ICollection<Server>? Servers { get; set; }
|
||||
public virtual ICollection<Redistributable>? Redistributables { get; set; }
|
||||
|
||||
public string? ValidKeyRegex { get; set; }
|
||||
public virtual ICollection<Key>? Keys { get; set; }
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace LANCommander.Data.Models
|
||||
{
|
||||
[Table("Redistributables")]
|
||||
public class Redistributable : BaseModel
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
|
||||
public virtual ICollection<Archive>? Archives { get; set; }
|
||||
public virtual ICollection<Script>? Scripts { get; set; }
|
||||
public virtual ICollection<Game>? Games { get; set; }
|
||||
}
|
||||
}
|
|
@ -18,5 +18,11 @@ namespace LANCommander.Data.Models
|
|||
[ForeignKey(nameof(GameId))]
|
||||
[InverseProperty("Scripts")]
|
||||
public virtual Game? Game { get; set; }
|
||||
|
||||
public Guid? RedistributableId { get; set; }
|
||||
[JsonIgnore]
|
||||
[ForeignKey(nameof(RedistributableId))]
|
||||
[InverseProperty("Scripts")]
|
||||
public virtual Redistributable? Redistributable { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddRedistributables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GameId",
|
||||
table: "Scripts",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "TEXT");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RedistributableId",
|
||||
table: "Scripts",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GameId",
|
||||
table: "Archive",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "TEXT");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "RedistributableId",
|
||||
table: "Archive",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Redistributables",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Notes = table.Column<string>(type: "TEXT", nullable: true),
|
||||
CreatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
CreatedById = table.Column<Guid>(type: "TEXT", nullable: true),
|
||||
UpdatedOn = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
UpdatedById = table.Column<Guid>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Redistributables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Redistributables_AspNetUsers_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Redistributables_AspNetUsers_UpdatedById",
|
||||
column: x => x.UpdatedById,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Scripts_RedistributableId",
|
||||
table: "Scripts",
|
||||
column: "RedistributableId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Archive_RedistributableId",
|
||||
table: "Archive",
|
||||
column: "RedistributableId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Redistributables_CreatedById",
|
||||
table: "Redistributables",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Redistributables_UpdatedById",
|
||||
table: "Redistributables",
|
||||
column: "UpdatedById");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Archive_Redistributables_RedistributableId",
|
||||
table: "Archive",
|
||||
column: "RedistributableId",
|
||||
principalTable: "Redistributables",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Scripts_Redistributables_RedistributableId",
|
||||
table: "Scripts",
|
||||
column: "RedistributableId",
|
||||
principalTable: "Redistributables",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Archive_Redistributables_RedistributableId",
|
||||
table: "Archive");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Scripts_Redistributables_RedistributableId",
|
||||
table: "Scripts");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Redistributables");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Scripts_RedistributableId",
|
||||
table: "Scripts");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Archive_RedistributableId",
|
||||
table: "Archive");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RedistributableId",
|
||||
table: "Scripts");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RedistributableId",
|
||||
table: "Archive");
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GameId",
|
||||
table: "Scripts",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "GameId",
|
||||
table: "Archive",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
1554
LANCommander/Migrations/20231019005133_AddGameRedistributableRelationship.Designer.cs
generated
Normal file
1554
LANCommander/Migrations/20231019005133_AddGameRedistributableRelationship.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddGameRedistributableRelationship : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "GameRedistributable",
|
||||
columns: table => new
|
||||
{
|
||||
GameId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
RedistributableId = table.Column<Guid>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_GameRedistributable", x => new { x.GameId, x.RedistributableId });
|
||||
table.ForeignKey(
|
||||
name: "FK_GameRedistributable_Games_GameId",
|
||||
column: x => x.GameId,
|
||||
principalTable: "Games",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_GameRedistributable_Redistributables_RedistributableId",
|
||||
column: x => x.RedistributableId,
|
||||
principalTable: "Redistributables",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_GameRedistributable_RedistributableId",
|
||||
table: "GameRedistributable",
|
||||
column: "RedistributableId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "GameRedistributable");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,6 +81,21 @@ namespace LANCommander.Migrations
|
|||
b.ToTable("GamePublisher");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameRedistributable", b =>
|
||||
{
|
||||
b.Property<Guid>("GameId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("RedistributableId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("GameId", "RedistributableId");
|
||||
|
||||
b.HasIndex("RedistributableId");
|
||||
|
||||
b.ToTable("GameRedistributable");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameTag", b =>
|
||||
{
|
||||
b.Property<Guid>("GamesId")
|
||||
|
@ -165,7 +180,7 @@ namespace LANCommander.Migrations
|
|||
b.Property<DateTime>("CreatedOn")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("GameId")
|
||||
b.Property<Guid?>("GameId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("LastVersionId")
|
||||
|
@ -175,6 +190,9 @@ namespace LANCommander.Migrations
|
|||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("RedistributableId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("UncompressedSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -196,6 +214,8 @@ namespace LANCommander.Migrations
|
|||
|
||||
b.HasIndex("LastVersionId");
|
||||
|
||||
b.HasIndex("RedistributableId");
|
||||
|
||||
b.HasIndex("UpdatedById");
|
||||
|
||||
b.ToTable("Archive");
|
||||
|
@ -505,6 +525,43 @@ namespace LANCommander.Migrations
|
|||
b.ToTable("MultiplayerModes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("CreatedById")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedOn")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedOn")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("UpdatedById");
|
||||
|
||||
b.ToTable("Redistributables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.Role", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
@ -591,13 +648,15 @@ namespace LANCommander.Migrations
|
|||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("GameId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("RedistributableId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("RequiresAdmin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -616,6 +675,8 @@ namespace LANCommander.Migrations
|
|||
|
||||
b.HasIndex("GameId");
|
||||
|
||||
b.HasIndex("RedistributableId");
|
||||
|
||||
b.HasIndex("UpdatedById");
|
||||
|
||||
b.ToTable("Scripts");
|
||||
|
@ -1027,6 +1088,21 @@ namespace LANCommander.Migrations
|
|||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameRedistributable", b =>
|
||||
{
|
||||
b.HasOne("LANCommander.Data.Models.Game", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("GameId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.Redistributable", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RedistributableId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("GameTag", b =>
|
||||
{
|
||||
b.HasOne("LANCommander.Data.Models.Game", null)
|
||||
|
@ -1074,13 +1150,17 @@ namespace LANCommander.Migrations
|
|||
b.HasOne("LANCommander.Data.Models.Game", "Game")
|
||||
.WithMany("Archives")
|
||||
.HasForeignKey("GameId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.Archive", "LastVersion")
|
||||
.WithMany()
|
||||
.HasForeignKey("LastVersionId");
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.Redistributable", "Redistributable")
|
||||
.WithMany("Archives")
|
||||
.HasForeignKey("RedistributableId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedById");
|
||||
|
@ -1091,6 +1171,8 @@ namespace LANCommander.Migrations
|
|||
|
||||
b.Navigation("LastVersion");
|
||||
|
||||
b.Navigation("Redistributable");
|
||||
|
||||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
|
@ -1243,6 +1325,21 @@ namespace LANCommander.Migrations
|
|||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b =>
|
||||
{
|
||||
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedById");
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedById");
|
||||
|
||||
b.Navigation("CreatedBy");
|
||||
|
||||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.SavePath", b =>
|
||||
{
|
||||
b.HasOne("LANCommander.Data.Models.User", "CreatedBy")
|
||||
|
@ -1273,8 +1370,12 @@ namespace LANCommander.Migrations
|
|||
b.HasOne("LANCommander.Data.Models.Game", "Game")
|
||||
.WithMany("Scripts")
|
||||
.HasForeignKey("GameId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.Redistributable", "Redistributable")
|
||||
.WithMany("Scripts")
|
||||
.HasForeignKey("RedistributableId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("LANCommander.Data.Models.User", "UpdatedBy")
|
||||
.WithMany()
|
||||
|
@ -1284,6 +1385,8 @@ namespace LANCommander.Migrations
|
|||
|
||||
b.Navigation("Game");
|
||||
|
||||
b.Navigation("Redistributable");
|
||||
|
||||
b.Navigation("UpdatedBy");
|
||||
});
|
||||
|
||||
|
@ -1426,6 +1529,13 @@ namespace LANCommander.Migrations
|
|||
b.Navigation("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.Redistributable", b =>
|
||||
{
|
||||
b.Navigation("Archives");
|
||||
|
||||
b.Navigation("Scripts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("LANCommander.Data.Models.Server", b =>
|
||||
{
|
||||
b.Navigation("ServerConsoles");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@page "/Games/{id:guid}/{panel}"
|
||||
@page "/Games/Add"
|
||||
@using LANCommander.Components.FileManagerComponents;
|
||||
@using LANCommander.Data.Enums;
|
||||
@using LANCommander.Models;
|
||||
@using LANCommander.Pages.Games.Components
|
||||
@using System.IO.Compression;
|
||||
|
@ -12,6 +13,7 @@
|
|||
@inject TagService TagService
|
||||
@inject ArchiveService ArchiveService
|
||||
@inject ScriptService ScriptService
|
||||
@inject RedistributableService RedistributableService
|
||||
@inject IMessageService MessageService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ModalService ModalService
|
||||
|
@ -97,6 +99,9 @@
|
|||
<FormItem Label="Tags">
|
||||
<TagsInput Entities="Tags" @bind-Values="Game.Tags" OptionLabelSelector="c => c.Name" TItem="Data.Models.Tag" />
|
||||
</FormItem>
|
||||
<FormItem Label="Redistributables">
|
||||
<TransferInput LeftTitle="Available" RightTitle="Selected" DataSource="Redistributables" TitleSelector="r => r.Name" @bind-Values="Game.Redistributables" />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
|
@ -121,11 +126,11 @@
|
|||
</div>
|
||||
|
||||
<div data-panel="Scripts">
|
||||
<ScriptEditor @bind-Scripts="Game.Scripts" GameId="Game.Id" ArchiveId="@LatestArchiveId" />
|
||||
<ScriptEditor @bind-Scripts="Game.Scripts" GameId="Game.Id" ArchiveId="@LatestArchiveId" AllowedTypes="new ScriptType[] { ScriptType.Install, ScriptType.Uninstall, ScriptType.NameChange, ScriptType.KeyChange }" />
|
||||
</div>
|
||||
|
||||
<div data-panel="Archives">
|
||||
<ArchiveEditor Game="Game" />
|
||||
<ArchiveEditor @bind-Archives="Game.Archives" GameId="Game.Id" />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -160,6 +165,9 @@ else
|
|||
IEnumerable<Company> Companies;
|
||||
IEnumerable<Genre> Genres;
|
||||
IEnumerable<Data.Models.Tag> Tags;
|
||||
IEnumerable<Redistributable> Redistributables = new List<Redistributable>();
|
||||
IEnumerable<TransferItem> RedistributableTargetItems = new List<TransferItem>();
|
||||
IEnumerable<string> TargetRedistributables = new List<string>();
|
||||
|
||||
FilePickerDialog ArchiveFilePickerDialog;
|
||||
|
||||
|
@ -206,6 +214,13 @@ else
|
|||
Companies = await CompanyService.Get();
|
||||
Genres = await GenreService.Get();
|
||||
Tags = await TagService.Get();
|
||||
Redistributables = await RedistributableService.Get();
|
||||
RedistributableTargetItems = Redistributables.Select(r => new TransferItem
|
||||
{
|
||||
Title = r.Name,
|
||||
Description = r.Description,
|
||||
Key = r.Id.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
@page "/Redistributables/{id:guid}"
|
||||
@page "/Redistributables/{id:guid}/{panel}"
|
||||
@page "/Redistributables/Add"
|
||||
@using LANCommander.Data.Enums;
|
||||
@inject RedistributableService RedistributableService
|
||||
@inject IMessageService MessageService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<Layout Class="panel-layout" Style="padding: 24px 0;">
|
||||
<Sider Width="200">
|
||||
<Menu Mode="@MenuMode.Inline" Style="height: 100%;">
|
||||
<MenuItem RouterLink="@($"/Redistributables/{Redistributable.Id}/General")">General</MenuItem>
|
||||
|
||||
@if (Redistributable.Id != Guid.Empty)
|
||||
{
|
||||
<MenuItem RouterLink="@($"/Redistributables/{Redistributable.Id}/Scripts")">Scripts</MenuItem>
|
||||
<MenuItem RouterLink="@($"/Redistributables/{Redistributable.Id}/Archives")">Archives</MenuItem>
|
||||
}
|
||||
</Menu>
|
||||
</Sider>
|
||||
|
||||
<Content>
|
||||
<PageHeader>
|
||||
<PageHeaderTitle>@Panel</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
|
||||
|
||||
<div class="panel-layout-content">
|
||||
@if (Panel == "General" || String.IsNullOrWhiteSpace(Panel))
|
||||
{
|
||||
<Form Model="@Redistributable" Layout="@FormLayout.Vertical">
|
||||
<FormItem Label="Name">
|
||||
<Input @bind-Value="@context.Name" />
|
||||
</FormItem>
|
||||
<FormItem Label="Notes">
|
||||
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
|
||||
</FormItem>
|
||||
<FormItem Label="Description">
|
||||
<TextArea @bind-Value="@context.Description" MaxLength=500 ShowCount />
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button Type="@ButtonType.Primary" OnClick="Save" Icon="@IconType.Fill.Save">Save</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
}
|
||||
|
||||
@if (Panel == "Scripts")
|
||||
{
|
||||
<ScriptEditor @bind-Scripts="Redistributable.Scripts" RedistributableId="Redistributable.Id" ArchiveId="@LatestArchiveId" AllowedTypes="new ScriptType[] { ScriptType.Install, ScriptType.DetectInstall }" />
|
||||
}
|
||||
|
||||
@if (Panel == "Archives")
|
||||
{
|
||||
<ArchiveEditor @bind-Archives="Redistributable.Archives" RedistributableId="Redistributable.Id" />
|
||||
}
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
@code {
|
||||
[Parameter] public Guid Id { get; set; }
|
||||
[Parameter] public string Panel { get; set; }
|
||||
|
||||
Redistributable Redistributable;
|
||||
|
||||
private Guid LatestArchiveId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Redistributable != null && Redistributable.Archives != null && Redistributable.Archives.Count > 0)
|
||||
return Redistributable.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Id;
|
||||
else
|
||||
return Guid.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Id == Guid.Empty)
|
||||
Redistributable = new Redistributable();
|
||||
else
|
||||
Redistributable = await RedistributableService.Get(Id);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Redistributable.Id != Guid.Empty)
|
||||
{
|
||||
Redistributable = await RedistributableService.Update(Redistributable);
|
||||
|
||||
await MessageService.Success("Redistributable updated!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Redistributable = await RedistributableService.Add(Redistributable);
|
||||
|
||||
NavigationManager.LocationChanged += NotifyRedistributableAdded;
|
||||
|
||||
NavigationManager.NavigateTo($"/Redistributables/{Redistributable.Id}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MessageService.Error("Could not save!");
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyRedistributableAdded(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
NavigationManager.LocationChanged -= NotifyRedistributableAdded;
|
||||
|
||||
MessageService.Success("Redistributable added!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
@page "/Redistributables"
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@attribute [Authorize]
|
||||
@inject RedistributableService RedistributableService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IMessageService MessageService
|
||||
|
||||
<PageHeader Title="Redistributables">
|
||||
<PageHeaderExtra>
|
||||
<Space Direction="DirectionVHType.Horizontal">
|
||||
<SpaceItem>
|
||||
<Search Placeholder="Search" @bind-Value="Search" BindOnInput DebounceMilliseconds="150" OnChange="() => LoadData()" />
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Button OnClick="() => Add()" Type="@ButtonType.Primary">Add Redistributable</Button>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</PageHeaderExtra>
|
||||
</PageHeader>
|
||||
|
||||
<TableColumnPicker @ref="Picker" Key="Redistributables" @bind-Visible="ColumnPickerVisible" />
|
||||
|
||||
<Table TItem="Redistributable" DataSource="@Redistributables" Loading="@Loading" PageSize="25" Responsive>
|
||||
<PropertyColumn Property="r => r.Name" Sortable Hidden="@(Picker.IsColumnHidden("Name"))" />
|
||||
<PropertyColumn Property="s => s.CreatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Created On"))" />
|
||||
<PropertyColumn Property="s => s.CreatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Created By"))">
|
||||
@context.CreatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<PropertyColumn Property="g => g.UpdatedOn" Format="MM/dd/yyyy hh:mm tt" Sortable Hidden="@(Picker.IsColumnHidden("Updated On"))" />
|
||||
<PropertyColumn Property="g => g.UpdatedBy" Sortable Hidden="@(Picker.IsColumnHidden("Updated By"))">
|
||||
@context.UpdatedBy?.UserName
|
||||
</PropertyColumn>
|
||||
<ActionColumn Title="" Style="text-align: right; white-space: nowrap">
|
||||
<TitleTemplate>
|
||||
<div style="text-align: right">
|
||||
<Button Icon="@IconType.Outline.Edit" Type="@ButtonType.Text" OnClick="() => OpenColumnPicker()" />
|
||||
</div>
|
||||
</TitleTemplate>
|
||||
<ChildContent>
|
||||
<Space Direction="DirectionVHType.Horizontal">
|
||||
<SpaceItem>
|
||||
<Button OnClick="() => Edit(context)">Edit</Button>
|
||||
</SpaceItem>
|
||||
<SpaceItem>
|
||||
<Popconfirm OnConfirm="() => Delete(context)" Title="Are you sure you want to delete this redistributable?">
|
||||
<Button Icon="@IconType.Outline.Close" Type="@ButtonType.Text" Danger />
|
||||
</Popconfirm>
|
||||
</SpaceItem>
|
||||
</Space>
|
||||
</ChildContent>
|
||||
</ActionColumn>
|
||||
</Table>
|
||||
|
||||
@code {
|
||||
IEnumerable<Redistributable> Redistributables { get; set; } = new List<Redistributable>();
|
||||
|
||||
bool Loading = true;
|
||||
|
||||
string Search = "";
|
||||
|
||||
TableColumnPicker Picker;
|
||||
bool ColumnPickerVisible = false;
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
LoadData();
|
||||
|
||||
Loading = false;
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadData()
|
||||
{
|
||||
var fuzzySearch = Search.ToLower().Trim();
|
||||
|
||||
Redistributables = await RedistributableService.Get(r => r.Name.ToLower().Contains(fuzzySearch)).OrderBy(r => r.Name).ToListAsync();
|
||||
}
|
||||
|
||||
private void Add()
|
||||
{
|
||||
NavigationManager.NavigateTo("/Redistributables/Add");
|
||||
}
|
||||
|
||||
private void Edit(Redistributable redistributable)
|
||||
{
|
||||
NavigationManager.NavigateTo($"/Redistributables/{redistributable.Id}/General");
|
||||
}
|
||||
|
||||
private async Task Delete(Redistributable redistributable)
|
||||
{
|
||||
Redistributables = new List<Redistributable>();
|
||||
|
||||
Loading = true;
|
||||
|
||||
await RedistributableService.Delete(redistributable);
|
||||
|
||||
Redistributables = await RedistributableService.Get(x => true).OrderBy(r => r.Name).ToListAsync();
|
||||
|
||||
Loading = false;
|
||||
}
|
||||
|
||||
private async Task OpenColumnPicker()
|
||||
{
|
||||
ColumnPickerVisible = true;
|
||||
}
|
||||
|
||||
private async Task CloseColumnPicker()
|
||||
{
|
||||
ColumnPickerVisible = false;
|
||||
}
|
||||
}
|
|
@ -141,6 +141,7 @@ namespace LANCommander
|
|||
builder.Services.AddScoped<ServerService>();
|
||||
builder.Services.AddScoped<ServerConsoleService>();
|
||||
builder.Services.AddScoped<GameSaveService>();
|
||||
builder.Services.AddScoped<RedistributableService>();
|
||||
|
||||
builder.Services.AddSingleton<ServerProcessService>();
|
||||
builder.Services.AddSingleton<IPXRelayService>();
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using LANCommander.Data;
|
||||
using LANCommander.Data.Models;
|
||||
|
||||
namespace LANCommander.Services
|
||||
{
|
||||
public class RedistributableService : BaseDatabaseService<Redistributable>
|
||||
{
|
||||
public RedistributableService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor) : base(dbContext, httpContextAccessor)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
@if (User != null && User.IsInRole("Administrator"))
|
||||
{
|
||||
<MenuItem RouterLink="/Games">Games</MenuItem>
|
||||
<MenuItem RouterLink="/Redistributables">Redistributables</MenuItem>
|
||||
<MenuItem RouterLink="/Servers">Servers</MenuItem>
|
||||
<MenuItem RouterLink="/Files">Files</MenuItem>
|
||||
<MenuItem RouterLink="/Settings">Settings</MenuItem>
|
||||
|
|
Loading…
Reference in New Issue