Remove icon extraction from WinPE files. Add migration from old extracted icons to new media entries.

This commit is contained in:
Pat Hartl 2023-11-03 01:24:27 -05:00
parent f0c8296b6e
commit f275d3478b
11 changed files with 1701 additions and 162 deletions

View file

@ -15,7 +15,6 @@ namespace LANCommander.SDK
public IEnumerable<string> Publishers { get; set; }
public IEnumerable<string> Developers { get; set; }
public string Version { get; set; }
public string Icon { get; set; }
public IEnumerable<GameAction> Actions { get; set; }
public bool Singleplayer { get; set; }
public MultiplayerInfo LocalMultiplayer { get; set; }

View file

@ -42,8 +42,6 @@ namespace LANCommander.Controllers.Api
{
var manifest = await GameService.GetManifest(id);
manifest.Icon = Url.Action(nameof(GetIcon), new { id = id });
return manifest;
}
@ -67,21 +65,5 @@ namespace LANCommander.Controllers.Api
return File(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read), "application/octet-stream", $"{game.Title.SanitizeFilename()}.zip");
}
[AllowAnonymous]
[HttpGet("{id}/Icon.png")]
public async Task<IActionResult> GetIcon(Guid id)
{
try
{
var game = await GameService.Get(id);
return File(GameService.GetIcon(game), "image/png");
}
catch (FileNotFoundException ex)
{
return NotFound();
}
}
}
}

View file

@ -10,7 +10,6 @@ namespace LANCommander.Data.Models
public string Title { get; set; }
[Display(Name = "Sort Title")]
public string? SortTitle { get; set; }
public string? Icon { get; set; }
[Display(Name = "Directory Name")]
public string? DirectoryName { get; set; }
public string? Description { get; set; }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,58 @@
using LANCommander.Data.Enums;
using LANCommander.Services;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LANCommander.Migrations
{
/// <inheritdoc />
public partial class RemoveGameIconProperty : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
var settings = SettingService.GetSettings();
// Migrate any old icons from the filesystem
if (Directory.Exists("Icon"))
foreach (var file in Directory.EnumerateFiles("Icon"))
{
var objectKey = Path.GetFileNameWithoutExtension(file);
var mediaId = Guid.NewGuid();
var fileId = Guid.NewGuid();
// Probably not an issue, but we'll check to make sure these are valid GUIDs just in case
if (Guid.TryParse(objectKey, out var gameId))
{
var sql = $@"
INSERT INTO Media
(Id, FileId, Type, SourceUrl, GameId, MimeType, CreatedOn, UpdatedOn)
SELECT
'{mediaId.ToString().ToUpper()}', '{fileId.ToString().ToUpper()}', '{(int)MediaType.Icon}', '', '{gameId.ToString().ToUpper()}', 'image/png', DateTime('now'), DateTime('now')
WHERE EXISTS (SELECT 1 FROM Games WHERE Id = '{gameId.ToString().ToUpper()}')
";
migrationBuilder.Sql(sql);
}
File.Move(file, Path.Combine(settings.Media.StoragePath, fileId.ToString()));
}
migrationBuilder.DropColumn(
name: "Icon",
table: "Games");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Icon",
table: "Games",
type: "TEXT",
nullable: true);
}
}
}

View file

@ -309,9 +309,6 @@ namespace LANCommander.Migrations
b.Property<long?>("IGDBId")
.HasColumnType("INTEGER");
b.Property<string>("Icon")
.HasColumnType("TEXT");
b.Property<string>("Notes")
.HasColumnType("TEXT");

View file

@ -73,9 +73,6 @@
<FormItem Label="Sort Title">
<Input @bind-Value="@context.SortTitle" />
</FormItem>
<FormItem Label="Icon">
<FilePicker @bind-Value="context.Icon" ArchiveId="@LatestArchiveId" AllowDirectories="true" />
</FormItem>
<FormItem Label="Notes">
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
</FormItem>
@ -260,34 +257,6 @@ else
MessageService.Success("Game added!");
}
private async Task BrowseForIcon()
{
var modalOptions = new ModalOptions()
{
Title = "Choose Icon",
Maximizable = false,
DefaultMaximized = true,
Closable = true,
OkText = "Select File"
};
var browserOptions = new FilePickerOptions()
{
ArchiveId = Game.Archives.FirstOrDefault().Id,
Select = true,
Multiple = false
};
var modalRef = await ModalService.CreateModalAsync<FilePickerDialog, FilePickerOptions, IEnumerable<IFileManagerEntry>>(modalOptions, browserOptions);
modalRef.OnOk = (results) =>
{
Game.Icon = results.FirstOrDefault().Path;
StateHasChanged();
return Task.CompletedTask;
};
}
private async Task OnGameLookupResultSelected(GameLookupResult result)
{
Game.Title = result.IGDBMetadata.Name;

View file

@ -145,7 +145,12 @@
private string GetIcon(Game game)
{
return $"/api/Games/{game.Id}/Icon.png";
var media = game?.Media?.FirstOrDefault(m => m.Type == Data.Enums.MediaType.Icon);
if (media != null)
return $"/api/Media/{media.Id}/Download?fileId={media.FileId}";
else
return "/favicon.ico";
}
private void Add()

View file

@ -123,7 +123,12 @@
private string GetIcon(Game game)
{
return $"/api/Games/{game?.Id}/Icon.png";
var media = game?.Media?.FirstOrDefault(m => m.Type == Data.Enums.MediaType.Icon);
if (media != null)
return $"/api/Media/{media.Id}/Download?fileId={media.FileId}";
else
return "/favicon.ico";
}
private async Task StartServers()

View file

@ -8,14 +8,6 @@
<PageHeader Title="Tools" />
<div style="padding: 0 24px;">
<h3>Icon Cache</h3>
<p>
If your icons look wrong or need to be refreshed, click the button below to clear the icon cache. Note that icons will only be regenerated when requested.
</p>
<Button Type="@ButtonType.Primary" OnClick="ClearIconCache" Loading="ClearingIconCache">Clear Icon Cache</Button>
<Divider />
<h3>Recalculate File Sizes</h3>
<p>
Some file sizes are cached in the database. Click the button below to scan through all files and recalculate their size.
@ -37,27 +29,8 @@
@code {
bool ClearingIconCache = false;
bool RecalculatingFileSizes = false;
async Task ClearIconCache()
{
ClearingIconCache = true;
foreach (var icon in Directory.GetFiles("Icon"))
{
try
{
File.Delete(icon);
}
catch { }
}
ClearingIconCache = false;
await MessageService.Success("Icon cache cleared!");
}
async Task RecalculateFileSizes()
{
RecalculatingFileSizes = true;

View file

@ -12,10 +12,12 @@ namespace LANCommander.Services
public class GameService : BaseDatabaseService<Game>
{
private readonly ArchiveService ArchiveService;
private readonly MediaService MediaService;
public GameService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor, ArchiveService archiveService) : base(dbContext, httpContextAccessor)
public GameService(DatabaseContext dbContext, IHttpContextAccessor httpContextAccessor, ArchiveService archiveService, MediaService mediaService) : base(dbContext, httpContextAccessor)
{
ArchiveService = archiveService;
MediaService = mediaService;
}
public override async Task Delete(Game game)
@ -23,8 +25,11 @@ namespace LANCommander.Services
foreach (var archive in game.Archives.OrderByDescending(a => a.CreatedOn))
{
await ArchiveService.Delete(archive);
}
FileHelpers.DeleteIfExists($"Icon/{game.Id}.png".ToPath());
foreach (var media in game.Media)
{
await MediaService.Delete(media);
}
await base.Delete(game);
@ -44,7 +49,6 @@ namespace LANCommander.Services
Description = game.Description,
ReleasedOn = game.ReleasedOn.GetValueOrDefault(),
Singleplayer = game.Singleplayer,
Icon = game.Icon
};
if (game.Genres != null && game.Genres.Count > 0)
@ -115,81 +119,5 @@ namespace LANCommander.Services
return manifest;
}
public byte[] GetIcon(Game game)
{
var cachedPath = $"Icon/{game.Id}.png";
if (File.Exists(cachedPath))
return File.ReadAllBytes(cachedPath);
else
{
#if WINDOWS
try
{
if (game.Archives == null || game.Archives.Count == 0)
throw new FileNotFoundException();
var archive = game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault();
Bitmap bitmap = null;
var iconReference = ArchiveService.ReadFile(archive.ObjectKey, game.Icon);
if (IsWinPEFile(iconReference))
{
var tmp = System.IO.Path.GetTempFileName();
System.IO.File.WriteAllBytes(tmp, iconReference);
var icon = System.Drawing.Icon.ExtractAssociatedIcon(tmp);
bitmap = icon.ToBitmap();
}
else
{
using (var ms = new MemoryStream(iconReference))
{
bitmap = (Bitmap)Bitmap.FromStream(ms);
}
}
var iconPng = ConvertToPng(bitmap);
File.WriteAllBytes(cachedPath, iconPng);
return iconPng;
}
catch (Exception ex)
{
}
#endif
return File.ReadAllBytes("favicon.png");
}
}
private static bool IsWinPEFile(byte[] file)
{
var mz = new byte[2];
using (var ms = new MemoryStream(file))
{
ms.Read(mz, 0, 2);
}
return System.Text.Encoding.UTF8.GetString(mz) == "MZ";
}
private static byte[] ConvertToPng(Image img)
{
using (var stream = new MemoryStream())
{
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return stream.ToArray();
}
}
}
}