Merge branch 'redistributables'
This commit is contained in:
		
						commit
						ff9ec5a17b
					
				
					 30 changed files with 4042 additions and 36 deletions
				
			
		|  | @ -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; } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										15
									
								
								LANCommander.SDK/Models/Redistributable.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								LANCommander.SDK/Models/Redistributable.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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(""); | ||||
							
								
								
									
										37
									
								
								LANCommander/Components/TransferInput.razor
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								LANCommander/Components/TransferInput.razor
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										57
									
								
								LANCommander/Controllers/Api/RedistributableController.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								LANCommander/Controllers/Api/RedistributableController.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; } | ||||
|  |  | |||
							
								
								
									
										16
									
								
								LANCommander/Data/Models/Redistributable.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								LANCommander/Data/Models/Redistributable.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; } | ||||
|     } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										1524
									
								
								LANCommander/Migrations/20231018060152_AddRedistributables.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1524
									
								
								LANCommander/Migrations/20231018060152_AddRedistributables.Designer.cs
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										158
									
								
								LANCommander/Migrations/20231018060152_AddRedistributables.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								LANCommander/Migrations/20231018060152_AddRedistributables.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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() | ||||
|  |  | |||
							
								
								
									
										117
									
								
								LANCommander/Pages/Redistributables/Edit.razor
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								LANCommander/Pages/Redistributables/Edit.razor
									
										
									
									
									
										Normal file
									
								
							|  | @ -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!"); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										115
									
								
								LANCommander/Pages/Redistributables/Index.razor
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								LANCommander/Pages/Redistributables/Index.razor
									
										
									
									
									
										Normal file
									
								
							|  | @ -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>(); | ||||
|  |  | |||
							
								
								
									
										12
									
								
								LANCommander/Services/RedistributableService.cs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								LANCommander/Services/RedistributableService.cs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		
		Reference in a new issue
	
	 Pat Hartl
						Pat Hartl