mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-16 12:38:02 -04:00
Merge pull request #1746 from rmcrackan/rmcrackan/1744-images
#1744 - attempt to harded code for cover image/folder image
This commit is contained in:
@@ -5,6 +5,7 @@ namespace LibationFileManager;
|
||||
public interface IInteropFunctions
|
||||
{
|
||||
void SetFolderIcon(string image, string directory);
|
||||
void SetFolderIcon(byte[] imageJpegBytes, string directory);
|
||||
void DeleteFolderIcon(string directory);
|
||||
Process? RunAsRoot(string exe, string args);
|
||||
void InstallUpgrade(string upgradeBundle);
|
||||
|
||||
@@ -10,6 +10,7 @@ public class NullInteropFunctions : IInteropFunctions
|
||||
public NullInteropFunctions(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void SetFolderIcon(byte[] imageJpegBytes, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
public bool CanUpgrade => throw new PlatformNotSupportedException();
|
||||
public string ReleaseIdString => throw new PlatformNotSupportedException();
|
||||
|
||||
@@ -88,25 +88,30 @@ public static class PictureStorage
|
||||
{
|
||||
lock (cacheLocker)
|
||||
{
|
||||
if (!cache.ContainsKey(def) || cache[def] is null)
|
||||
var path = getPath(def);
|
||||
|
||||
// Disk is authoritative. Ignore in-memory cache when the file is missing so a later
|
||||
// successful download (or a CDN that omits Content-Length) is not blocked by a stale placeholder.
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var path = getPath(def);
|
||||
var bytes
|
||||
= File.Exists(path)
|
||||
? File.ReadAllBytes(path)
|
||||
: downloadBytes(def, cancellationToken);
|
||||
cache[def] = bytes;
|
||||
cache[def] = File.ReadAllBytes(path);
|
||||
return cache[def];
|
||||
}
|
||||
return cache[def];
|
||||
|
||||
if (cache.ContainsKey(def))
|
||||
cache.Remove(def);
|
||||
|
||||
var bytes = downloadBytes(def, cancellationToken);
|
||||
if (File.Exists(path))
|
||||
cache[def] = bytes;
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetDefaultImage(PictureSize pictureSize, byte[] bytes)
|
||||
=> defaultImages[pictureSize] = bytes;
|
||||
public static byte[] GetDefaultImage(PictureSize size)
|
||||
=> defaultImages.ContainsKey(size)
|
||||
? defaultImages[size]
|
||||
: new byte[0];
|
||||
=> defaultImages.TryGetValue(size, out byte[]? value) ? value : [];
|
||||
|
||||
static void BackgroundDownloader()
|
||||
{
|
||||
@@ -124,6 +129,9 @@ public static class PictureStorage
|
||||
}
|
||||
|
||||
private static HttpClient imageDownloadClient { get; } = new HttpClient();
|
||||
|
||||
private const long MaxPictureDownloadBytes = 25 * 1024 * 1024;
|
||||
|
||||
private static byte[] downloadBytes(PictureDefinition def, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (def.PictureId is null)
|
||||
@@ -136,14 +144,36 @@ public static class PictureStorage
|
||||
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, "ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}{sizeStr}.jpg");
|
||||
using var response = imageDownloadClient.Send(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).EnsureSuccessStatusCode();
|
||||
|
||||
if (response.Content.Headers.ContentLength is not long size)
|
||||
return GetDefaultImage(def.Size);
|
||||
byte[] bytes;
|
||||
if (response.Content.Headers.ContentLength is long knownSize && knownSize >= 0)
|
||||
{
|
||||
if (knownSize == 0 || knownSize > MaxPictureDownloadBytes)
|
||||
return GetDefaultImage(def.Size);
|
||||
|
||||
var bytes = new byte[size];
|
||||
using var respStream = response.Content.ReadAsStream(cancellationToken);
|
||||
respStream.ReadExactly(bytes);
|
||||
bytes = new byte[knownSize];
|
||||
using (var respStream = response.Content.ReadAsStream(cancellationToken))
|
||||
respStream.ReadExactly(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Chunked responses often omit Content-Length; the previous implementation treated that as failure,
|
||||
// left no file on disk, and callers that opened the expected path then broke (e.g. folder icons).
|
||||
using var respStream = response.Content.ReadAsStream(cancellationToken);
|
||||
using var ms = new MemoryStream();
|
||||
var buffer = new byte[81920];
|
||||
int read;
|
||||
while ((read = respStream.Read(buffer, 0, buffer.Length)) > 0)
|
||||
{
|
||||
if (ms.Length + read > MaxPictureDownloadBytes)
|
||||
return GetDefaultImage(def.Size);
|
||||
ms.Write(buffer, 0, read);
|
||||
}
|
||||
|
||||
bytes = ms.ToArray();
|
||||
if (bytes.Length == 0)
|
||||
return GetDefaultImage(def.Size);
|
||||
}
|
||||
|
||||
// save image file. make sure to not save default image
|
||||
var path = getPath(def);
|
||||
File.WriteAllBytes(path, bytes);
|
||||
|
||||
|
||||
@@ -18,9 +18,16 @@ public static class WindowsDirectory
|
||||
return;
|
||||
}
|
||||
|
||||
// get path of cover art in Images dir. Download first if not exists
|
||||
var coverArtPath = PictureStorage.GetPicturePathSynchronously(new(pictureId, PictureSize._300x300), cancellationToken);
|
||||
InteropFactory.Create().SetFolderIcon(image: coverArtPath, directory: directory);
|
||||
// Load JPEG bytes from Images cache (or download). Prefer bytes → ICO so we never depend on a
|
||||
// path that might not exist when Amazon omits Content-Length or another downloader left a stale cache entry.
|
||||
var jpegBytes = PictureStorage.GetPictureSynchronously(new(pictureId, PictureSize._300x300), cancellationToken);
|
||||
if (jpegBytes.Length == 0)
|
||||
{
|
||||
Serilog.Log.Logger.Warning("Cover art unavailable for folder icon (empty image). {@DebugInfo}", new { directory, pictureId });
|
||||
return;
|
||||
}
|
||||
|
||||
InteropFactory.Create().SetFolderIcon(imageJpegBytes: jpegBytes, directory: directory);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ internal class LinuxInterop : IInteropFunctions
|
||||
public LinuxInterop(params object[] values) { }
|
||||
|
||||
public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException();
|
||||
public void SetFolderIcon(byte[] imageJpegBytes, string directory) => throw new PlatformNotSupportedException();
|
||||
public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException();
|
||||
|
||||
public string ReleaseIdString => LibationScaffolding.ReleaseIdentifier.ToString() + (File.Exists("/bin/apt") ? "_DEB" : "_RPM");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace MacOSConfigApp;
|
||||
|
||||
@@ -14,6 +15,25 @@ internal class MacOSInterop : IInteropFunctions
|
||||
{
|
||||
Process.Start("fileicon", $"set {directory.SurroundWithQuotes()} {image.SurroundWithQuotes()}").WaitForExit();
|
||||
}
|
||||
|
||||
public void SetFolderIcon(byte[] imageJpegBytes, string directory)
|
||||
{
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), $"LibationFolderIcon-{Guid.NewGuid():N}.jpg");
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(tempPath, imageJpegBytes);
|
||||
SetFolderIcon(tempPath, directory);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempPath))
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
catch { /* best effort */ }
|
||||
}
|
||||
}
|
||||
public void DeleteFolderIcon(string directory)
|
||||
{
|
||||
Process.Start("fileicon", $"rm {directory.SurroundWithQuotes()}").WaitForExit();
|
||||
|
||||
@@ -13,7 +13,15 @@ internal class WinInterop : IInteropFunctions
|
||||
public WinInterop(params object[] values) { }
|
||||
public void SetFolderIcon(string image, string directory)
|
||||
{
|
||||
var icon = Image.Load(image).ToIcon();
|
||||
using var img = Image.Load(image);
|
||||
var icon = img.ToIcon();
|
||||
new DirectoryInfo(directory)?.SetIcon(icon, "Music");
|
||||
}
|
||||
|
||||
public void SetFolderIcon(byte[] imageJpegBytes, string directory)
|
||||
{
|
||||
using var img = Image.Load(new MemoryStream(imageJpegBytes, writable: false));
|
||||
var icon = img.ToIcon();
|
||||
new DirectoryInfo(directory)?.SetIcon(icon, "Music");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user