Only download missing plugin images

This commit is contained in:
Shadowghost
2026-09-07 10:33:08 +02:00
parent 3e7d1158da
commit e1e319cb5c
2 changed files with 87 additions and 22 deletions
@@ -395,29 +395,11 @@ namespace Emby.Server.Implementations.Plugins
var url = new Uri(packageInfo.ImageUrl);
imagePath = Path.Join(path, url.Segments[^1]);
var fileStream = AsyncFile.OpenWrite(imagePath);
Stream? downloadStream = null;
try
// The catalog is refreshed on every dashboard visit and rewrites the manifest of
// every installed plugin, so only fetch an image that is actually missing.
if (!ImageExists(imagePath))
{
downloadStream = await HttpClientFactory
.CreateClient(NamedClient.Default)
.GetStreamAsync(url)
.ConfigureAwait(false);
await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
imagePath = string.Empty;
}
finally
{
await fileStream.DisposeAsync().ConfigureAwait(false);
if (downloadStream is not null)
{
await downloadStream.DisposeAsync().ConfigureAwait(false);
}
imagePath = await DownloadImage(url, imagePath).ConfigureAwait(false);
}
}
@@ -456,6 +438,67 @@ namespace Emby.Server.Implementations.Plugins
}
}
private static bool ImageExists(string imagePath)
{
var image = new FileInfo(imagePath);
// A previous download may have been interrupted, leaving an empty file behind.
return image.Exists && image.Length > 0;
}
private async Task<string> DownloadImage(Uri url, string imagePath)
{
// Download to a temporary file and move it into place, so that neither a failed download
// nor a concurrent one can be observed as a partially written image.
var tempPath = imagePath + "." + Path.GetRandomFileName();
try
{
var fileStream = AsyncFile.Create(tempPath);
Stream? downloadStream = null;
try
{
downloadStream = await HttpClientFactory
.CreateClient(NamedClient.Default)
.GetStreamAsync(url)
.ConfigureAwait(false);
await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false);
}
finally
{
await fileStream.DisposeAsync().ConfigureAwait(false);
if (downloadStream is not null)
{
await downloadStream.DisposeAsync().ConfigureAwait(false);
}
}
File.Move(tempPath, imagePath, true);
return imagePath;
}
catch (Exception ex) when (ex is HttpRequestException or IOException or UnauthorizedAccessException)
{
_logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
TryDeleteFile(tempPath);
return string.Empty;
}
}
private void TryDeleteFile(string path)
{
try
{
File.Delete(path);
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
_logger.LogWarning(ex, "Unable to delete {Path}.", path);
}
}
/// <summary>
/// Reconciles the manifest against any properties that exist locally in a pre-packaged meta.json found at the path.
/// If no file is found, no reconciliation occurs.