diff --git a/Directory.Packages.props b/Directory.Packages.props
index 596512f..5b6b6e9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -27,6 +27,7 @@
+
diff --git a/src/SharpIDE.Application/Features/Nuget/NugetPackageIconCacheService.cs b/src/SharpIDE.Application/Features/Nuget/NugetPackageIconCacheService.cs
new file mode 100644
index 0000000..484f299
--- /dev/null
+++ b/src/SharpIDE.Application/Features/Nuget/NugetPackageIconCacheService.cs
@@ -0,0 +1,52 @@
+namespace SharpIDE.Application.Features.Nuget;
+
+public enum NugetPackageIconFormat
+{
+ Png,
+ Jpg
+}
+public class NugetPackageIconCacheService(IHttpClientFactory httpClientFactory)
+{
+ private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
+
+ public async Task<(byte[]? bytes, NugetPackageIconFormat?)> GetNugetPackageIcon(string packageId, Uri? iconUrl)
+ {
+ var appdataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ var cacheFolder = Path.Combine(appdataFolderPath, "SharpIDE", "NugetPackageIconCache");
+ Directory.CreateDirectory(cacheFolder);
+ var packageIconFilePath = Path.Combine(cacheFolder, $"{packageId}.bin");
+ if (File.Exists(packageIconFilePath))
+ {
+ var bytes = await File.ReadAllBytesAsync(packageIconFilePath);
+ return (bytes, GetImageFormat(bytes));
+ }
+ else if (iconUrl is null)
+ {
+ return (null, null);
+ }
+ else
+ {
+ var httpClient = _httpClientFactory.CreateClient();
+ var iconBytes = await httpClient.GetByteArrayAsync(iconUrl);
+ await File.WriteAllBytesAsync(packageIconFilePath, iconBytes);
+ return (iconBytes, GetImageFormat(iconBytes));
+ }
+ }
+
+ private static NugetPackageIconFormat? GetImageFormat(byte[] imageBytes)
+ {
+ // PNG files start with 89 50 4E 47 0D 0A 1A 0A
+ if (imageBytes is [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, ..])
+ {
+ return NugetPackageIconFormat.Png;
+ }
+
+ // JPEG files start with FF D8 and end with FF D9
+ if (imageBytes is [0xFF, 0xD8, .., 0xFF, 0xD9])
+ {
+ return NugetPackageIconFormat.Jpg;
+ }
+
+ return null;
+ }
+}
diff --git a/src/SharpIDE.Application/SharpIDE.Application.csproj b/src/SharpIDE.Application/SharpIDE.Application.csproj
index 4324197..93c437e 100644
--- a/src/SharpIDE.Application/SharpIDE.Application.csproj
+++ b/src/SharpIDE.Application/SharpIDE.Application.csproj
@@ -27,6 +27,7 @@
+
diff --git a/src/SharpIDE.Godot/DiAutoload.cs b/src/SharpIDE.Godot/DiAutoload.cs
index dd97a48..ef7285b 100644
--- a/src/SharpIDE.Godot/DiAutoload.cs
+++ b/src/SharpIDE.Godot/DiAutoload.cs
@@ -38,12 +38,15 @@ public partial class DiAutoload : Node
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
+
+ services.AddHttpClient();
services.AddLogging(builder =>
{
builder.AddConsole();
diff --git a/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs b/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs
index 992b18c..1e4985f 100644
--- a/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs
+++ b/src/SharpIDE.Godot/Features/Nuget/PackageEntry.cs
@@ -17,6 +17,8 @@ public partial class PackageEntry : MarginContainer
private static readonly Color Source_4_Color = new Color("966a00");
private static readonly Color Source_5_Color = new Color("efaeae");
+ [Inject] private readonly NugetPackageIconCacheService _nugetPackageIconCacheService = null!;
+
public IdePackageResult PackageResult { get; set; } = null!;
public override void _Ready()
{
@@ -35,26 +37,24 @@ public partial class PackageEntry : MarginContainer
_currentVersionLabel.Text = string.Empty;
//_latestVersionLabel.Text = $"Latest: {PackageResult.PackageSearchMetadata.vers.LatestVersion}";
_sourceNamesContainer.QueueFreeChildren();
-
- var iconUrl = PackageResult.PackageSearchMetadata.IconUrl;
- if (iconUrl != null)
+
+ _ = Task.GodotRun(async () =>
{
- var httpRequest = new HttpRequest(); // Godot's abstraction
- AddChild(httpRequest);
- httpRequest.RequestCompleted += (result, responseCode, headers, body) =>
+ var (iconBytes, iconFormat) = await _nugetPackageIconCacheService.GetNugetPackageIcon(PackageResult.PackageSearchMetadata.Identity.Id, PackageResult.PackageSearchMetadata.IconUrl);
+ var image = new Image();
+ var error = iconFormat switch
{
- if (responseCode is 200)
- {
- var image = new Image();
- image.LoadPngFromBuffer(body);
- image.Resize(32, 32, Image.Interpolation.Lanczos);
- var loadedImageTexture = ImageTexture.CreateFromImage(image);
- _packageIconTextureRect.Texture = loadedImageTexture;
- }
- httpRequest.QueueFree();
+ NugetPackageIconFormat.Png => image.LoadPngFromBuffer(iconBytes),
+ NugetPackageIconFormat.Jpg => image.LoadJpgFromBuffer(iconBytes),
+ _ => Error.FileUnrecognized
};
- httpRequest.Request(iconUrl.ToString());
- }
+ if (error is Error.Ok)
+ {
+ image.Resize(32, 32, Image.Interpolation.Lanczos); // Probably should cache resized images instead
+ var loadedImageTexture = ImageTexture.CreateFromImage(image);
+ await this.InvokeAsync(() => _packageIconTextureRect.Texture = loadedImageTexture);
+ }
+ });
foreach (var source in PackageResult.PackageSources)
{
diff --git a/src/SharpIDE.Godot/SharpIDE.Godot.csproj b/src/SharpIDE.Godot/SharpIDE.Godot.csproj
index 2da594c..cf93d7b 100644
--- a/src/SharpIDE.Godot/SharpIDE.Godot.csproj
+++ b/src/SharpIDE.Godot/SharpIDE.Godot.csproj
@@ -10,6 +10,7 @@
+