diff --git a/src/UniGetUI.Core.IconEngine.Tests/IconCacheEngineTests.cs b/src/UniGetUI.Core.IconEngine.Tests/IconCacheEngineTests.cs index af8023774..9502afb1b 100644 --- a/src/UniGetUI.Core.IconEngine.Tests/IconCacheEngineTests.cs +++ b/src/UniGetUI.Core.IconEngine.Tests/IconCacheEngineTests.cs @@ -4,33 +4,58 @@ namespace UniGetUI.Core.IconEngine.Tests { public static class IconCacheEngineTests { - [Theory] - [InlineData("https://marticliment.com/resources/wingetui.png", - new byte[] { 0x24, 0x4e, 0x42, 0xb6, 0xbe, 0x44, 0x04, 0x66, 0xc8, 0x77, 0xf7, 0x68, 0x8a, 0xe0, 0xa9, 0x45, 0xfb, 0x2e, 0x66, 0x8c, 0x41, 0x84, 0x1f, 0x2d, 0x10, 0xcf, 0x92, 0xd4, 0x0d, 0x8c, 0xbb, 0xf6 }, - "TestManager", "Package1")] - public static async Task TestCacheEngineForSha256(string url, byte[] data, string managerName, string packageId) + [Fact] + public static async Task TestCacheEngineForSha256() { - string extension = url.Split(".")[^1]; + Uri ICON_1 = new Uri("https://marticliment.com/resources/wingetui.png"); + byte[] HASH_1 = [0x24, 0x4e, 0x42, 0xb6, 0xbe, 0x44, 0x04, 0x66, 0xc8, 0x77, 0xf7, 0x68, 0x8a, 0xe0, 0xa9, 0x45, 0xfb, 0x2e, 0x66, 0x8c, 0x41, 0x84, 0x1f, 0x2d, 0x10, 0xcf, 0x92, 0xd4, 0x0d, 0x8c, 0xbb, 0xf6]; + Uri ICON_2 = new Uri("https://marticliment.com/resources/elevenclock.png"); + byte[] HASH_2 = [0x9E, 0xB8, 0x7A, 0x5A, 0x64, 0xCA, 0x6D, 0x8D, 0x0A, 0x7B, 0x98, 0xC5, 0x4F, 0x6A, 0x58, 0x72, 0xFD, 0x94, 0xC9, 0xA6, 0x82, 0xB3, 0x2B, 0x90, 0x70, 0x66, 0x66, 0x1C, 0xBF, 0x81, 0x97, 0x97]; + + string managerName = "TestManager"; + string packageId = "Package55"; + + string extension = ICON_1.ToString().Split(".")[^1]; string expectedFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, managerName, packageId + "." + extension); if (File.Exists(expectedFile)) { File.Delete(expectedFile); } - CacheableIcon icon = new(new Uri(url), data); - string path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + // Download a hashed icon + CacheableIcon icon = new(ICON_1, HASH_1); + string? path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); DateTime oldModificationDate = File.GetLastWriteTime(path); - icon = new CacheableIcon(new Uri(url.Replace("icon", "nonexistingicon")), data); - path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + // Test the same icon, modification date shouldn't change + icon = new CacheableIcon(ICON_1, HASH_1); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); DateTime newModificationDate = File.GetLastWriteTime(path); Assert.Equal(oldModificationDate, newModificationDate); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); + + // Attempt to retrieve a different icon. The modification date SHOULD have changed + icon = new CacheableIcon(ICON_2, HASH_2); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); + DateTime newIconModificationDate = File.GetLastWriteTime(path); + + Assert.NotEqual(oldModificationDate, newIconModificationDate); + Assert.Equal(expectedFile, path); + Assert.True(File.Exists(path)); + + // Give an invalid hash: The icon should not be cached not returned + icon = new CacheableIcon(ICON_2, HASH_1); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.Null(path); + Assert.False(File.Exists(path)); } [Theory] @@ -44,23 +69,29 @@ public static async Task TestCacheEngineForPackageVersion(string url, string ver File.Delete(expectedFile); } + // Download an icon through version verification CacheableIcon icon = new(new Uri(url), version); - string path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + string? path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); DateTime oldModificationDate = File.GetLastWriteTime(path); + // Test the same version, the icon should not get touched icon = new CacheableIcon(new Uri(url), version); - path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); DateTime newModificationDate = File.GetLastWriteTime(path); Assert.Equal(oldModificationDate, newModificationDate); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); + // Test a new version, the icon should be downloaded again icon = new CacheableIcon(new Uri(url), version + "-beta0"); - path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); DateTime newNewModificationDate = File.GetLastWriteTime(path); Assert.NotEqual(oldModificationDate, newNewModificationDate); @@ -68,39 +99,104 @@ public static async Task TestCacheEngineForPackageVersion(string url, string ver Assert.True(File.Exists(path)); } - [Theory] - [InlineData("https://marticliment.com/resources/wingetui.png", 47903, "TestManager", "Package3")] - public static async Task TestCacheEngineForPackageSize(string url, long size, string managerName, string packageId) + [Fact] + public static async Task TestCacheEngineForIconUri() { - string extension = url.Split(".")[^1]; + Uri URI_1 = new Uri("https://marticliment.com/resources/wingetui.png"); + Uri URI_2 = new Uri("https://marticliment.com/resources/elevenclock.png"); + string managerName = "TestManager"; + string packageId = "Package12"; + + string extension = URI_1.ToString().Split(".")[^1]; string expectedFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, managerName, packageId + "." + extension); if (File.Exists(expectedFile)) { File.Delete(expectedFile); } - CacheableIcon icon = new(new Uri(url), size); - string path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + // Download an icon through URI verification + CacheableIcon icon = new(URI_1); + string? path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); DateTime oldModificationDate = File.GetLastWriteTime(path); - icon = new CacheableIcon(new Uri(url), size); - path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + // Test the same URI, the icon should not get touched + icon = new CacheableIcon(URI_1); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); DateTime newModificationDate = File.GetLastWriteTime(path); Assert.Equal(oldModificationDate, newModificationDate); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); - icon = new CacheableIcon(new Uri(url), size + 1); - path = await IconCacheEngine.DownloadIconOrCache(icon, managerName, packageId); + // Test a new URI, the icon should be downloaded again + icon = new CacheableIcon(URI_2); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); DateTime newNewModificationDate = File.GetLastWriteTime(path); Assert.NotEqual(oldModificationDate, newNewModificationDate); Assert.Equal(expectedFile, path); Assert.True(File.Exists(path)); } + + [Fact] + public static async Task TestCacheEngineForPackageSize() + { + Uri ICON_1 = new Uri("https://marticliment.com/resources/wingetui.png"); + int ICON_1_SIZE = 47903; + Uri ICON_2 = new Uri("https://marticliment.com/resources/elevenclock.png"); + int ICON_2_SIZE = 19747; + string managerName = "TestManager"; + string packageId = "Package3"; + + // Clear any cache for reproducable data + string extension = ICON_1.ToString().Split(".")[^1]; + string expectedFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, managerName, packageId + "." + extension); + if (File.Exists(expectedFile)) + { + File.Delete(expectedFile); + } + + // Cache an icon + CacheableIcon icon = new(ICON_1, ICON_1_SIZE); + string? path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); + Assert.Equal(expectedFile, path); + Assert.True(File.Exists(path)); + + DateTime oldModificationDate = File.GetLastWriteTime(path); + + // Attempt to retrieve the same icon again. + icon = new CacheableIcon(ICON_1, ICON_1_SIZE); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); + DateTime newModificationDate = File.GetLastWriteTime(path); + + // The modification date shouldn't have changed + Assert.Equal(oldModificationDate, newModificationDate); + Assert.Equal(expectedFile, path); + Assert.True(File.Exists(path)); + + // Attempt to retrieve a different icon. The modification date SHOULD have changed + icon = new CacheableIcon(ICON_2, ICON_2_SIZE); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.NotNull(path); + DateTime newIconModificationDate = File.GetLastWriteTime(path); + + Assert.NotEqual(oldModificationDate, newIconModificationDate); + Assert.Equal(expectedFile, path); + Assert.True(File.Exists(path)); + + // Give an invalid size: The icon should not be cached not returned + icon = new CacheableIcon(ICON_1, ICON_1_SIZE + 1); + path = await IconCacheEngine.GetCacheOrDownloadIcon(icon, managerName, packageId); + Assert.Null(path); + Assert.False(File.Exists(path)); + } } } diff --git a/src/UniGetUI.Core.IconStore/IconCacheEngine.cs b/src/UniGetUI.Core.IconStore/IconCacheEngine.cs index 27ca6b6f7..228bfb8a5 100644 --- a/src/UniGetUI.Core.IconStore/IconCacheEngine.cs +++ b/src/UniGetUI.Core.IconStore/IconCacheEngine.cs @@ -5,10 +5,9 @@ namespace UniGetUI.Core.IconEngine { - public enum CachedIconVerificationMethod + public enum IconValidationMethod { - None, - Sha256Checksum, + SHA256, FileSize, PackageVersion, UriSource @@ -17,36 +16,55 @@ public enum CachedIconVerificationMethod public readonly struct CacheableIcon { public readonly Uri Url; - public readonly byte[] Sha256 = []; + public readonly byte[] SHA256 = []; public readonly string Version = ""; public readonly long Size = -1; - public readonly CachedIconVerificationMethod VerificationMethod; + public readonly IconValidationMethod ValidationMethod; + /// + /// Build a cacheable icon with SHA256 verification + /// + /// + /// public CacheableIcon(Uri uri, byte[] Sha256) { Url = uri; - this.Sha256 = Sha256; - VerificationMethod = CachedIconVerificationMethod.Sha256Checksum; + this.SHA256 = Sha256; + ValidationMethod = IconValidationMethod.SHA256; } + /// + /// Build a cacheable icon with Version verification + /// + /// + /// public CacheableIcon(Uri uri, string version) { Url = uri; Version = version; - VerificationMethod = CachedIconVerificationMethod.PackageVersion; + ValidationMethod = IconValidationMethod.PackageVersion; } + /// + /// Build a cacheable icon with Size verification + /// + /// + /// public CacheableIcon(Uri uri, long size) { Url = uri; Size = size; - VerificationMethod = CachedIconVerificationMethod.FileSize; + ValidationMethod = IconValidationMethod.FileSize; } + /// + /// Build a cacheable icon with Uri verification + /// + /// public CacheableIcon(Uri icon) { Url = icon; - VerificationMethod = CachedIconVerificationMethod.UriSource; + ValidationMethod = IconValidationMethod.UriSource; } } @@ -59,11 +77,11 @@ public static class IconCacheEngine /// The name of the PackageManager /// the Id of the package /// A path to a local icon file - public static async Task DownloadIconOrCache(CacheableIcon? _icon, string ManagerName, string PackageId) + public static async Task GetCacheOrDownloadIcon(CacheableIcon? _icon, string ManagerName, string PackageId) { if (_icon is null) { - return ""; + return null; } var icon = (CacheableIcon)_icon; @@ -76,121 +94,182 @@ public static async Task DownloadIconOrCache(CacheableIcon? _icon, strin extension = "png"; } - string FilePath = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}"); - string VersionPath = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}.version"); - string UriPath = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}.uri"); - string FileDirectory = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName); - if (!Directory.Exists(FileDirectory)) + string cachedIconFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}"); + string iconVersionFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}.version"); + string iconUriFile = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName, $"{PackageId}.{extension}.uri"); + string iconLocation = Path.Join(CoreData.UniGetUICacheDirectory_Icons, ManagerName); + if (!Directory.Exists(iconLocation)) { - Directory.CreateDirectory(FileDirectory); + Directory.CreateDirectory(iconLocation); } - bool FileExists = File.Exists(FilePath); - bool IsFileValid = false; - if (FileExists) + // Verify if the cached icon is valid + bool isLocalCacheValid = false; + bool localCacheExists = File.Exists(cachedIconFile); + if (localCacheExists) { - switch (icon.VerificationMethod) - { - case CachedIconVerificationMethod.FileSize: - try - { - long size = await CoreTools.GetFileSizeAsyncAsLong(icon.Url); - IsFileValid = size == icon.Size; - } - catch (Exception e) - { - Logger.Warn($"Failed to verify icon file size for {icon.Url} through FileSize with error {e.Message}"); - } - break; - - case CachedIconVerificationMethod.Sha256Checksum: - try - { - byte[] hash = await CalculateFileHashAsync(FilePath); - IsFileValid = hash.SequenceEqual(icon.Sha256); - } - catch (Exception e) - { - Logger.Warn($"Failed to verify icon file size for {icon.Url} through Sha256 with error {e.Message}"); - } - break; - - case CachedIconVerificationMethod.PackageVersion: - try - { - if (File.Exists(VersionPath)) - { - string localVersion = File.ReadAllText(VersionPath); - IsFileValid = localVersion == icon.Version; - } - } - catch (Exception e) - { - Logger.Warn($"Failed to verify icon file size for {icon.Url} through PackageVersion with error {e.Message}"); - } - break; - - case CachedIconVerificationMethod.UriSource: - try - { - if (File.Exists(UriPath)) - { - string localVersion = File.ReadAllText(UriPath); - IsFileValid = localVersion == icon.Url.ToString(); - } - } - catch (Exception e) - { - Logger.Warn($"Failed to verify icon file size for {icon.Url} through UriSource with error {e.Message}"); - } - break; - - default: - Logger.ImportantInfo($"Icon {icon.Url} for package {PackageId} on manager {ManagerName} does not use a valid cache verification method"); - IsFileValid = true; - break; - } - } - - Logger.Debug($"Icon for package {PackageId} on manager {ManagerName} with Uri={icon.Url} has been determined to be {(IsFileValid ? "VALID" : "INVALID")} through verification method {icon.VerificationMethod}"); - - if (!IsFileValid) - { - using HttpClient client = new(CoreData.GenericHttpClientParameters); - client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); - if (File.Exists(FilePath)) + isLocalCacheValid = icon.ValidationMethod switch { - File.Delete(FilePath); - } + IconValidationMethod.FileSize => ValidateByImageSize(icon, cachedIconFile), + IconValidationMethod.SHA256 => await ValidateBySHA256(icon, cachedIconFile), + IconValidationMethod.PackageVersion => await ValidateByVersion(icon, iconVersionFile), + IconValidationMethod.UriSource => await ValidateByUri(icon, iconUriFile), + _ => throw new InvalidDataException("Invalid icon validation method"), + }; + } - HttpResponseMessage response = await client.GetAsync(icon.Url); - response.EnsureSuccessStatusCode(); - using (Stream stream = await response.Content.ReadAsStreamAsync()) - using (FileStream fileStream = File.Create(FilePath)) - { - await stream.CopyToAsync(fileStream); - } + // If a valid cache was found, return that cache + if (isLocalCacheValid) + { + Logger.Debug($"Icon for package {PackageId} is VALID and won't be downloaded again (verification method is {icon.ValidationMethod})"); + return cachedIconFile; + // Exit the function + } + else if (localCacheExists) + { + Logger.ImportantInfo($"Icon for Package={PackageId} Manager={ManagerName} Uri={icon.Url} is NOT VALID (verification method is {icon.ValidationMethod})"); + } + else + { + Logger.Debug($"Icon for package {PackageId} on manager {ManagerName} was not found on cache, downloading it..."); + } - if (icon.VerificationMethod == CachedIconVerificationMethod.PackageVersion) - { - await File.WriteAllTextAsync(VersionPath, icon.Version); - } + // If the cache is determined to NOT be valid, delete cache + DeteteCachedFiles(cachedIconFile, iconVersionFile, iconUriFile); - if (icon.VerificationMethod == CachedIconVerificationMethod.UriSource) - { - await File.WriteAllTextAsync(UriPath, icon.Url.ToString()); - } + // After discarding the cache, regenerate it + using HttpClient client = new(CoreData.GenericHttpClientParameters); + client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); + HttpResponseMessage response = await client.GetAsync(icon.Url); + response.EnsureSuccessStatusCode(); + using (Stream stream = await response.Content.ReadAsStreamAsync()) + using (FileStream fileStream = File.Create(cachedIconFile)) + { + await stream.CopyToAsync(fileStream); } - Logger.Info($"Icon for package {PackageId} stored on {FilePath}"); - return FilePath; + if (icon.ValidationMethod is IconValidationMethod.PackageVersion) + await File.WriteAllTextAsync(iconVersionFile, icon.Version); + + if (icon.ValidationMethod is IconValidationMethod.UriSource) + await File.WriteAllTextAsync(iconUriFile, icon.Url.ToString()); + + Logger.Info($"Icon for package {PackageId} stored on {cachedIconFile}"); + + // Ensure the new icon has been properly downloaded + bool isNewCacheValid = icon.ValidationMethod switch + { + IconValidationMethod.FileSize => ValidateByImageSize(icon, cachedIconFile), + IconValidationMethod.SHA256 => await ValidateBySHA256(icon, cachedIconFile), + IconValidationMethod.PackageVersion => true, // The validation result would be always true + IconValidationMethod.UriSource => true, // The validation result would be always true + _ => throw new InvalidDataException("Invalid icon validation method"), + }; + + if (isNewCacheValid) + { + Logger.Info($"NEWLY DOWNLOADED Icon for Package={PackageId} Manager={ManagerName} Uri={icon.Url} is VALID (verification method is {icon.ValidationMethod})"); + return cachedIconFile; + } + else + { + Logger.Warn($"NEWLY DOWNLOADED Icon for Pacakge={PackageId} Manager={ManagerName} Uri={icon.Url} is NOT VALID and will be discarded (verification method is {icon.ValidationMethod})"); + DeteteCachedFiles(cachedIconFile, iconVersionFile, iconUriFile); + return null; + } } - private static async Task CalculateFileHashAsync(string filePath) + /// + /// Checks whether a cached image is valid or not depending on the size (in bytes) of the image + /// + /// + /// + /// + private static bool ValidateByImageSize(CacheableIcon icon, string cachedIconPath) { - using FileStream stream = File.OpenRead(filePath); - using SHA256 sha256 = SHA256.Create(); - return await sha256.ComputeHashAsync(stream); + try + { + FileInfo fileInfo = new FileInfo(cachedIconPath); + return icon.Size == fileInfo.Length; + } + catch (Exception e) + { + Logger.Warn($"Failed to verify icon file size for {icon.Url} via FileSize with error {e.Message}"); + return false; + } + } + + /// + /// Checks whether a cached image is valid or not depending on its SHA256 hash + /// + /// + /// + /// + private static async Task ValidateBySHA256(CacheableIcon icon, string cachedIconPath) + { + try + { + using FileStream stream = File.OpenRead(cachedIconPath); + using SHA256 sha256 = SHA256.Create(); + return (await sha256.ComputeHashAsync(stream)).SequenceEqual(icon.SHA256); + } + catch (Exception e) + { + Logger.Warn($"Failed to verify icon file size for {icon.Url} via Sha256 with error {e.Message}"); + return false; + } + } + + /// + /// Checks whether a cached image is valid or not depending on the package version it was pulled from + /// + /// + /// + /// + private static async Task ValidateByVersion(CacheableIcon icon, string versionPath) + { + try + { + return File.Exists(versionPath) && (await File.ReadAllTextAsync(versionPath)) == icon.Version; + } + catch (Exception e) + { + Logger.Warn($"Failed to verify icon file size for {icon.Url} via PackageVersion with error {e.Message}"); + return false; + } + } + + /// + /// Checks whether a cached image is valid or not depending on the URI it was pulled from + /// + /// + /// + /// + private static async Task ValidateByUri(CacheableIcon icon, string uriPath) + { + try + { + return File.Exists(uriPath) && (await File.ReadAllTextAsync(uriPath)) == icon.Url.ToString(); + } + catch (Exception e) + { + Logger.Warn($"Failed to verify icon file size for {icon.Url} via UriSource with error {e.Message}"); + return false; + } + } + + private static void DeteteCachedFiles(string iconFile, string versionFile, string uriFile) + { + try + { + if (File.Exists(iconFile)) File.Delete(iconFile); + if (File.Exists(versionFile)) File.Delete(versionFile); + if (File.Exists(uriFile)) File.Delete(uriFile); + } + catch (Exception e) + { + Logger.Warn($"An error occurred while deleting old icon cache: {e.Message}"); + } } } } diff --git a/src/UniGetUI.PackageEngine.Managers.WinGet/Providers/WinGetPackageDetailsProvider.cs b/src/UniGetUI.PackageEngine.Managers.WinGet/Providers/WinGetPackageDetailsProvider.cs index b9027ec6c..8db36f951 100644 --- a/src/UniGetUI.PackageEngine.Managers.WinGet/Providers/WinGetPackageDetailsProvider.cs +++ b/src/UniGetUI.PackageEngine.Managers.WinGet/Providers/WinGetPackageDetailsProvider.cs @@ -39,9 +39,9 @@ protected override void GetDetails_UnSafe(IPackageDetails details) return GetMicrosoftStoreIcon(package); } - Logger.Warn("Non-MSStore WinGet Native Icons have been forcefully disabled on code"); - return null; - //return await GetWinGetPackageIcon(package); + // Logger.Warn("Non-MSStore WinGet Native Icons have been forcefully disabled on code"); + // return null; + return GetWinGetPackageIcon(package); } protected override IEnumerable GetScreenshots_UnSafe(IPackage package) @@ -222,7 +222,6 @@ protected override IEnumerable GetScreenshots_UnSafe(IPackage package) return new CacheableIcon(new Uri(uri)); } - // TODO: Need to work on retrieving WinGet icons private static CacheableIcon? GetWinGetPackageIcon(IPackage package) { if (WinGetHelper.Instance is not NativeWinGetHelper) @@ -245,7 +244,6 @@ protected override IEnumerable GetScreenshots_UnSafe(IPackage package) // Connect to catalog Catalog.AcceptSourceAgreements = true; ConnectResult ConnectResult = Catalog.Connect(); - // ConnectResult ConnectResult = await Catalog.ConnectAsync(); if (ConnectResult.Status != ConnectResultStatus.Ok) { Logger.Error("[WINGET COM] Failed to connect to catalog " + package.Source.Name); @@ -264,7 +262,7 @@ protected override IEnumerable GetScreenshots_UnSafe(IPackage package) if (SearchResult.Matches is null || SearchResult.Matches.Count == 0) { - Logger.Error("[WINGET COM] Failed to find package " + package.Id + " in catalog " + package.Source.Name); + Logger.Error($"[WINGET COM] Package with Id={package.Id} was NOT found in catalog id=" + package.Source.Name); return null; } @@ -274,15 +272,18 @@ protected override IEnumerable GetScreenshots_UnSafe(IPackage package) // Extract data from NativeDetails CatalogPackageMetadata NativeDetails = NativePackage.DefaultInstallVersion.GetCatalogPackageMetadata(); - CacheableIcon? Icon = null; - + // Get the actual icon and return it foreach (Icon? icon in NativeDetails.Icons.ToArray()) { - Icon = new CacheableIcon(new Uri(icon.Url), icon.Sha256); - Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}"); + if (icon is not null && icon.Url is not null) + { + Logger.Debug($"Found WinGet native icon for {package.Id} with URL={icon.Url}"); + return new CacheableIcon(new Uri(icon.Url), icon.Sha256); + } } - return Icon; + Logger.Debug($"Native WinGet icon for Package={package.Id} on catalog={package.Source.Name} was not found :("); + return null; } } } diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs index ebe1d8977..a8632a0d5 100644 --- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs +++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Packages/Package.cs @@ -178,11 +178,11 @@ public virtual Uri GetIconUrl() try { CacheableIcon? icon = Manager.GetPackageIconUrl(this); - string path = IconCacheEngine.DownloadIconOrCache(icon, Manager.Name, Id).GetAwaiter().GetResult(); + string? path = IconCacheEngine.GetCacheOrDownloadIcon(icon, Manager.Name, Id).GetAwaiter().GetResult(); Uri Icon; - if (path == "") Icon = new Uri("ms-appx:///Assets/Images/package_color.png"); - else Icon = new Uri("file:///" + path); + if (path is null) Icon = new Uri("ms-appx:///Assets/Images/package_color.png"); + else Icon = new Uri("file:///" + path); Logger.Debug($"Icon for package {Id} was loaded from {Icon}"); return Icon; diff --git a/src/UniGetUI/Pages/SettingsPage.xaml b/src/UniGetUI/Pages/SettingsPage.xaml index 8e79db45a..6d94b0395 100644 --- a/src/UniGetUI/Pages/SettingsPage.xaml +++ b/src/UniGetUI/Pages/SettingsPage.xaml @@ -89,9 +89,15 @@ ButtonText="Export" Click="ExportSettings" /> + diff --git a/src/UniGetUI/Pages/SettingsPage.xaml.cs b/src/UniGetUI/Pages/SettingsPage.xaml.cs index f194dce6b..a326392df 100644 --- a/src/UniGetUI/Pages/SettingsPage.xaml.cs +++ b/src/UniGetUI/Pages/SettingsPage.xaml.cs @@ -312,20 +312,20 @@ void EnableOrDisableEntries() EnableOrDisableEntries(); MainLayout.Children.Add(ManagerExpander); - } - } - public MainWindow GetWindow() - { - return MainApp.Instance.MainWindow; - } - public int GetHwnd() - { - return (int)WinRT.Interop.WindowNative.GetWindowHandle(GetWindow()); + LoadIconCacheSize(); + } } - private void OpenWelcomeWizard(object sender, EventArgs e) + private async void LoadIconCacheSize() { + double realSize = (await Task.Run(() => + { + return Directory.GetFiles(CoreData.UniGetUICacheDirectory_Icons, "*", SearchOption.AllDirectories) + .Sum(file => new FileInfo(file).Length); + })) / 1048576d; + double roundedSize = ((int)(realSize*100))/100d; + ResetIconCache.Header = CoreTools.Translate("The local icon cache currently takes {0} MB", roundedSize); } private void ImportSettings(object sender, EventArgs e) @@ -475,20 +475,6 @@ private void DisableDownloadingNewTranslations_StateChanged(object sender, Event private void TextboxCard_ValueChanged(object sender, EventArgs e) { ExperimentalSettingsExpander.ShowRestartRequiredBanner(); } - private void ResetIconCache_Click(object sender, EventArgs e) - { - try - { - Directory.Delete(CoreData.UniGetUICacheDirectory_Icons, true); - } - catch (Exception ex) - { - Logger.Error("An error occurred while deleting icon cache"); - Logger.Error(ex); - } - ExperimentalSettingsExpander.ShowRestartRequiredBanner(); - } - private async void DoBackup_Click(object sender, EventArgs e) { DialogHelper.ShowLoadingDialog(CoreTools.Translate("Performing backup, please wait...")); @@ -555,5 +541,20 @@ private void UseUserGSudoToggle_StateChanged(object sender, EventArgs e) { ExperimentalSettingsExpander.ShowRestartRequiredBanner(); } + + private void ResetIconCache_OnClick(object? sender, EventArgs e) + { + try + { + Directory.Delete(CoreData.UniGetUICacheDirectory_Icons, true); + } + catch (Exception ex) + { + Logger.Error("An error occurred while deleting icon cache"); + Logger.Error(ex); + } + GeneralSettingsExpander.ShowRestartRequiredBanner(); + LoadIconCacheSize(); + } } }