mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-09 08:04:13 -04:00
Remove unused parameters Remove unnecessary casts Make fields readonly Order modifiers Format document Sort usings Remove unnecessary nullable directive Apply namespace preferences (file-level)
124 lines
4.6 KiB
C#
124 lines
4.6 KiB
C#
using System;
|
|
using System.Threading;
|
|
|
|
namespace LibationFileManager;
|
|
|
|
public static class WindowsDirectory
|
|
{
|
|
const int FolderIconMaxAttempts = 5;
|
|
|
|
public static void SetCoverAsFolderIcon(string? pictureId, string directory, CancellationToken cancellationToken)
|
|
{
|
|
//Currently only works for Windows and macOS
|
|
if (!Configuration.Instance.UseCoverAsFolderIcon)
|
|
return;
|
|
if (string.IsNullOrEmpty(pictureId))
|
|
{
|
|
Serilog.Log.Logger.Warning("No picture ID provided to set cover art as folder icon. {@DebugInfo}", new { directory });
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
|
|
for (var attempt = 1; attempt <= FolderIconMaxAttempts; attempt++)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
try
|
|
{
|
|
var jpegBytes = PictureStorage.GetPictureSynchronously(new(pictureId, PictureSize._300x300), cancellationToken);
|
|
if (jpegBytes.Length == 0)
|
|
{
|
|
if (attempt < FolderIconMaxAttempts)
|
|
{
|
|
Serilog.Log.Logger.Debug("Folder icon: empty 300x300 image on attempt {Attempt}/{Max}; retrying after delay. {@DebugInfo}", attempt, FolderIconMaxAttempts, new { directory, pictureId });
|
|
DelayBetweenFolderIconRetries(cancellationToken, attempt);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
InteropFactory.Create().SetFolderIcon(imageJpegBytes: jpegBytes, directory: directory);
|
|
return;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (attempt < FolderIconMaxAttempts)
|
|
{
|
|
Serilog.Log.Logger.Debug(ex, "Folder icon: attempt {Attempt}/{Max} failed; retrying after delay. {@DebugInfo}", attempt, FolderIconMaxAttempts, new { directory, pictureId });
|
|
DelayBetweenFolderIconRetries(cancellationToken, attempt);
|
|
continue;
|
|
}
|
|
|
|
if (TrySetFolderIconUsingPictureSize(pictureId, directory, PictureSize.Native, cancellationToken))
|
|
{
|
|
Serilog.Log.Logger.Information(
|
|
"Set Explorer folder icon using full-size cover after 300x300 failed (decode, ICO conversion, or writing desktop.ini/Icon.ico). {@DebugInfo}",
|
|
new { directory, pictureId });
|
|
return;
|
|
}
|
|
|
|
Serilog.Log.Logger.Error(ex,
|
|
"Could not set Explorer folder icon after {MaxAttempts} attempts (decode, ICO conversion, or writing desktop.ini/Icon.ico failed). The audiobook download itself should still be fine; try liberating again, or check folder permissions if the library is on removable media. {@DebugInfo}",
|
|
FolderIconMaxAttempts, new { directory, pictureId });
|
|
TryDeleteFolderIcon(directory);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 300x300 never returned usable bytes — Native is a separate CDN URL and is often already cached by DownloadCoverArt in the same session.
|
|
if (TrySetFolderIconUsingPictureSize(pictureId, directory, PictureSize.Native, cancellationToken))
|
|
{
|
|
Serilog.Log.Logger.Information(
|
|
"Set Explorer folder icon using full-size cover after 300x300 was empty or missing. {@DebugInfo}",
|
|
new { directory, pictureId });
|
|
return;
|
|
}
|
|
|
|
Serilog.Log.Logger.Warning(
|
|
"Could not set Explorer folder icon: neither 300x300 nor full-size cover became available. The audiobook download itself is unaffected. Check your network to Amazon images, disk space under Libation's Images folder, or try liberating again. {@DebugInfo}",
|
|
new { directory, pictureId });
|
|
TryDeleteFolderIcon(directory);
|
|
}
|
|
|
|
static bool TrySetFolderIconUsingPictureSize(string pictureId, string directory, PictureSize size, CancellationToken cancellationToken)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
try
|
|
{
|
|
var jpegBytes = PictureStorage.GetPictureSynchronously(new(pictureId, size), cancellationToken);
|
|
if (jpegBytes.Length == 0)
|
|
return false;
|
|
InteropFactory.Create().SetFolderIcon(imageJpegBytes: jpegBytes, directory: directory);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Logger.Debug(ex, "Folder icon: could not set using {PictureSize}. {@DebugInfo}", size, new { directory, pictureId });
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void DelayBetweenFolderIconRetries(CancellationToken cancellationToken, int attemptAfterFailure)
|
|
{
|
|
// 100, 200, 400, 800 ms; bounded backoff without Task.Delay allocation on hot path
|
|
var ms = 100 * (1 << (attemptAfterFailure - 1));
|
|
if (ms > 0)
|
|
cancellationToken.WaitHandle.WaitOne(ms);
|
|
}
|
|
|
|
static void TryDeleteFolderIcon(string directory)
|
|
{
|
|
try
|
|
{
|
|
InteropFactory.Create().DeleteFolderIcon(directory);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Serilog.Log.Logger.Error(ex, "Error rolling back folder icon files. {@DebugInfo}", new { directory });
|
|
}
|
|
}
|
|
}
|