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> Publishers { get; set; }
public IEnumerable<string> Developers { get; set; } public IEnumerable<string> Developers { get; set; }
public string Version { get; set; } public string Version { get; set; }
public string Icon { get; set; }
public IEnumerable<GameAction> Actions { get; set; } public IEnumerable<GameAction> Actions { get; set; }
public bool Singleplayer { get; set; } public bool Singleplayer { get; set; }
public MultiplayerInfo LocalMultiplayer { get; set; } public MultiplayerInfo LocalMultiplayer { get; set; }

View file

@ -42,8 +42,6 @@ namespace LANCommander.Controllers.Api
{ {
var manifest = await GameService.GetManifest(id); var manifest = await GameService.GetManifest(id);
manifest.Icon = Url.Action(nameof(GetIcon), new { id = id });
return manifest; 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"); 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; } public string Title { get; set; }
[Display(Name = "Sort Title")] [Display(Name = "Sort Title")]
public string? SortTitle { get; set; } public string? SortTitle { get; set; }
public string? Icon { get; set; }
[Display(Name = "Directory Name")] [Display(Name = "Directory Name")]
public string? DirectoryName { get; set; } public string? DirectoryName { get; set; }
public string? Description { 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") b.Property<long?>("IGDBId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Icon")
.HasColumnType("TEXT");
b.Property<string>("Notes") b.Property<string>("Notes")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

View file

@ -73,9 +73,6 @@
<FormItem Label="Sort Title"> <FormItem Label="Sort Title">
<Input @bind-Value="@context.SortTitle" /> <Input @bind-Value="@context.SortTitle" />
</FormItem> </FormItem>
<FormItem Label="Icon">
<FilePicker @bind-Value="context.Icon" ArchiveId="@LatestArchiveId" AllowDirectories="true" />
</FormItem>
<FormItem Label="Notes"> <FormItem Label="Notes">
<TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount /> <TextArea @bind-Value="@context.Notes" MaxLength=2000 ShowCount />
</FormItem> </FormItem>
@ -260,34 +257,6 @@ else
MessageService.Success("Game added!"); 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) private async Task OnGameLookupResultSelected(GameLookupResult result)
{ {
Game.Title = result.IGDBMetadata.Name; Game.Title = result.IGDBMetadata.Name;

View file

@ -145,7 +145,12 @@
private string GetIcon(Game game) 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() private void Add()

View file

@ -123,7 +123,12 @@
private string GetIcon(Game game) 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() private async Task StartServers()

View file

@ -8,14 +8,6 @@
<PageHeader Title="Tools" /> <PageHeader Title="Tools" />
<div style="padding: 0 24px;"> <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> <h3>Recalculate File Sizes</h3>
<p> <p>
Some file sizes are cached in the database. Click the button below to scan through all files and recalculate their size. 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 { @code {
bool ClearingIconCache = false;
bool RecalculatingFileSizes = 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() async Task RecalculateFileSizes()
{ {
RecalculatingFileSizes = true; RecalculatingFileSizes = true;

View file

@ -12,10 +12,12 @@ namespace LANCommander.Services
public class GameService : BaseDatabaseService<Game> public class GameService : BaseDatabaseService<Game>
{ {
private readonly ArchiveService ArchiveService; 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; ArchiveService = archiveService;
MediaService = mediaService;
} }
public override async Task Delete(Game game) public override async Task Delete(Game game)
@ -23,8 +25,11 @@ namespace LANCommander.Services
foreach (var archive in game.Archives.OrderByDescending(a => a.CreatedOn)) foreach (var archive in game.Archives.OrderByDescending(a => a.CreatedOn))
{ {
await ArchiveService.Delete(archive); 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); await base.Delete(game);
@ -44,7 +49,6 @@ namespace LANCommander.Services
Description = game.Description, Description = game.Description,
ReleasedOn = game.ReleasedOn.GetValueOrDefault(), ReleasedOn = game.ReleasedOn.GetValueOrDefault(),
Singleplayer = game.Singleplayer, Singleplayer = game.Singleplayer,
Icon = game.Icon
}; };
if (game.Genres != null && game.Genres.Count > 0) if (game.Genres != null && game.Genres.Count > 0)
@ -115,81 +119,5 @@ namespace LANCommander.Services
return manifest; 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();
}
}
} }
} }