Remove icon extraction from WinPE files. Add migration from old extracted icons to new media entries.
This commit is contained in:
parent
f0c8296b6e
commit
f275d3478b
11 changed files with 1701 additions and 162 deletions
|
@ -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; }
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
1624
LANCommander/Migrations/20231103052501_RemoveGameIconProperty.Designer.cs
generated
Normal file
1624
LANCommander/Migrations/20231103052501_RemoveGameIconProperty.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue