diff --git a/Shoko.Server/ImageDownload/ImageDownloadRequest.cs b/Shoko.Server/ImageDownload/ImageDownloadRequest.cs
deleted file mode 100644
index 5095f6261..000000000
--- a/Shoko.Server/ImageDownload/ImageDownloadRequest.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-using System.Net;
-using System.IO;
-using System.Threading;
-using System.Net.Http;
-using Shoko.Models.Server;
-using Shoko.Server.Extensions;
-using Shoko.Server.Models;
-using Shoko.Server.Server;
-using Shoko.Commons.Utils;
-using Shoko.Server.Providers.AniDB;
-
-#nullable enable
-namespace Shoko.Server.ImageDownload;
-
-///
-/// Represents the result of an image download operation.
-///
-public enum ImageDownloadResult
-{
- ///
- /// The image was successfully downloaded and saved.
- ///
- Success = 1,
-
- ///
- /// The image was not downloaded because it was already available in the cache.
- ///
- Cached = 2,
-
- ///
- /// The image could not be downloaded due to not being able to get the
- /// source or destination.
- ///
- Failure = 3,
-
- ///
- /// The image was not downloaded because the resource has been removed or is
- /// no longer available, but we could not remove the local entry because of
- /// it's type.
- ///
- InvalidResource = 4,
-
- ///
- /// The image was not downloaded because the resource has been removed or is
- /// no longer available, and thus have also been removed from the local
- /// database.
- ///
- RemovedResource = 5,
-}
-
-public class ImageDownloadRequest
-{
-
- private object ImageData { get; }
-
- public bool ForceDownload { get; }
-
- private string ImageServerUrl { get; }
-
- private string? _filePath { get; set; } = null;
-
- public string FilePath
- => _filePath != null ? _filePath : _filePath = ImageData switch
- {
- AniDB_Character character => character.GetPosterPath(),
- AniDB_Seiyuu creator => creator.GetPosterPath(),
- MovieDB_Fanart image => image.GetFullImagePath(),
- MovieDB_Poster image => image.GetFullImagePath(),
- SVR_AniDB_Anime anime => anime.PosterPath,
- TvDB_Episode episode => episode.GetFullImagePath(),
- TvDB_ImageFanart image => image.GetFullImagePath(),
- TvDB_ImagePoster image => image.GetFullImagePath(),
- TvDB_ImageWideBanner image => image.GetFullImagePath(),
- _ => string.Empty,
- };
-
- private string? _downloadUrl { get; set; } = null;
-
- public string DownloadUrl
- => _downloadUrl != null ? _downloadUrl : _downloadUrl = ImageData switch
- {
- AniDB_Character character => string.Format(ImageServerUrl, character.PicName),
- AniDB_Seiyuu creator => string.Format(ImageServerUrl, creator.PicName),
- MovieDB_Fanart movieFanart => string.Format(Constants.URLS.MovieDB_Images, movieFanart.URL),
- MovieDB_Poster moviePoster => string.Format(Constants.URLS.MovieDB_Images, moviePoster.URL),
- SVR_AniDB_Anime anime => string.Format(ImageServerUrl, anime.Picname),
- TvDB_Episode ep => string.Format(Constants.URLS.TvDB_Episode_Images, ep.Filename),
- TvDB_ImageFanart fanart => string.Format(Constants.URLS.TvDB_Images, fanart.BannerPath),
- TvDB_ImagePoster poster => string.Format(Constants.URLS.TvDB_Images, poster.BannerPath),
- TvDB_ImageWideBanner wideBanner => string.Format(Constants.URLS.TvDB_Images, wideBanner.BannerPath),
- _ => string.Empty
- };
-
- public bool IsImageValid
- => !string.IsNullOrEmpty(DownloadUrl) && !string.IsNullOrEmpty(FilePath) && File.Exists(FilePath) && Misc.IsImageValid(FilePath);
-
- private bool ShouldAniDBRateLimit
- => ImageData switch
- {
- AniDB_Character => true,
- AniDB_Seiyuu => true,
- SVR_AniDB_Anime => true,
- _ => false,
- };
-
- public ImageDownloadRequest(object data, bool forceDownload, string? imageServerUrl = null)
- {
- ImageData = data;
- ForceDownload = forceDownload;
- ImageServerUrl = imageServerUrl ?? "";
- }
-
- public ImageDownloadResult DownloadNow(int maxRetries = 5)
- => RecursivelyRetryDownload(0, maxRetries);
-
- private ImageDownloadResult RecursivelyRetryDownload(int count, int maxRetries)
- {
- // Abort if the download url or final destination is not available.
- if (string.IsNullOrEmpty(DownloadUrl) || string.IsNullOrEmpty(FilePath))
- return ImageDownloadResult.Failure;
-
- var imageValid = File.Exists(FilePath) && Misc.IsImageValid(FilePath);
- if (imageValid && !ForceDownload)
- return ImageDownloadResult.Cached;
-
- var tempPath = Path.Combine(ImageUtils.GetImagesTempFolder(), Path.GetFileName(FilePath));
- try
- {
- // Rate limit anidb image requests.
- if (ShouldAniDBRateLimit)
- AniDbImageRateLimiter.Instance.EnsureRate();
-
- // Ignore all certificate failures.
- ServicePointManager.Expect100Continue = true;
- ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
- ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
-
- // Download the image.
- using (var client = new HttpClient())
- {
- // Download the image data.
- client.DefaultRequestHeaders.Add("user-agent", "JMM");
- var bytes = client.GetByteArrayAsync(DownloadUrl)
- .ConfigureAwait(false)
- .GetAwaiter()
- .GetResult();
- if (bytes.Length < 4)
- throw new WebException(
- "The image download stream returned less than 4 bytes (a valid image has 2-4 bytes in the header)");
-
- // Check if the image format is valid.
- if (Misc.GetImageFormat(bytes) == null)
- throw new WebException("The image download stream returned an invalid image");
-
- // Delete the existing (failed?) temporary file.
- if (File.Exists(tempPath))
- File.Delete(tempPath);
-
- // Write the image data to the temp file.
- using (var fs = new FileStream(tempPath, FileMode.Create, FileAccess.Write))
- fs.Write(bytes, 0, bytes.Length);
-
- // Make sure the directory structure exists.
- var dirPath = Path.GetDirectoryName(FilePath);
- if (!string.IsNullOrEmpty(dirPath) && !Directory.Exists(dirPath))
- Directory.CreateDirectory(dirPath);
-
- // Delete the existing file if we're re-downloading.
- if (File.Exists(FilePath))
- {
- File.Delete(FilePath);
- }
-
- // Move the temp file to it's final destination.
- File.Move(tempPath, FilePath);
-
- return ImageDownloadResult.Success;
- }
- }
- catch (HttpRequestException ex)
- {
- // Mark the request as a failure if we received a 404 or 403.
- if (ex.StatusCode.HasValue && (ex.StatusCode.Value == HttpStatusCode.Forbidden || ex.StatusCode.Value == HttpStatusCode.NotFound))
- {
- var removed = RemoveResource();
- return removed ? ImageDownloadResult.RemovedResource : ImageDownloadResult.InvalidResource;
- }
-
- throw;
- }
- catch (WebException)
- {
- if (count + 1 >= maxRetries)
- throw;
-
- Thread.Sleep(1000);
- return RecursivelyRetryDownload(count + 1, maxRetries);
- }
- }
-
- private bool RemoveResource()
- {
- switch (ImageData)
- {
- case MovieDB_Fanart movieFanart:
- Repositories.RepoFactory.MovieDB_Fanart.Delete(movieFanart);
- return true;
- case MovieDB_Poster moviePoster:
- Repositories.RepoFactory.MovieDB_Poster.Delete(moviePoster);
- return true;
- case TvDB_ImageFanart tvdbFanart:
- Repositories.RepoFactory.TvDB_ImageFanart.Delete(tvdbFanart);
- return true;
- case TvDB_ImagePoster tvdbPoster:
- Repositories.RepoFactory.TvDB_ImagePoster.Delete(tvdbPoster);
- return true;
- case TvDB_ImageWideBanner tvdbWideBanner:
- Repositories.RepoFactory.TvDB_ImageWideBanner.Delete(tvdbWideBanner);
- return true;
- }
-
- return false;
- }
-}
diff --git a/Shoko.Server/ImageDownload/ImageDownloadResult.cs b/Shoko.Server/ImageDownload/ImageDownloadResult.cs
new file mode 100644
index 000000000..c054d0e07
--- /dev/null
+++ b/Shoko.Server/ImageDownload/ImageDownloadResult.cs
@@ -0,0 +1,38 @@
+#nullable enable
+namespace Shoko.Server.ImageDownload;
+
+///
+/// Represents the result of an image download operation.
+///
+public enum ImageDownloadResult
+{
+ ///
+ /// The image was successfully downloaded and saved.
+ ///
+ Success = 1,
+
+ ///
+ /// The image was not downloaded because it was already available in the cache.
+ ///
+ Cached = 2,
+
+ ///
+ /// The image could not be downloaded due to not being able to get the
+ /// source or destination.
+ ///
+ Failure = 3,
+
+ ///
+ /// The image was not downloaded because the resource has been removed or is
+ /// no longer available, but we could not remove the local entry because of
+ /// its type.
+ ///
+ InvalidResource = 4,
+
+ ///
+ /// The image was not downloaded because the resource has been removed or is
+ /// no longer available, and thus have also been removed from the local
+ /// database.
+ ///
+ RemovedResource = 5,
+}
diff --git a/Shoko.Server/Providers/AniDB/HTTP/AniDBHttpConnectionHandler.cs b/Shoko.Server/Providers/AniDB/HTTP/AniDBHttpConnectionHandler.cs
index d2da1e0ce..f3b3cba76 100644
--- a/Shoko.Server/Providers/AniDB/HTTP/AniDBHttpConnectionHandler.cs
+++ b/Shoko.Server/Providers/AniDB/HTTP/AniDBHttpConnectionHandler.cs
@@ -10,6 +10,7 @@ namespace Shoko.Server.Providers.AniDB.HTTP;
public class AniDBHttpConnectionHandler : ConnectionHandler, IHttpConnectionHandler
{
+ private readonly HttpClient _httpClient;
public override double BanTimerResetLength => 12;
public override string Type => "HTTP";
@@ -18,6 +19,14 @@ public class AniDBHttpConnectionHandler : ConnectionHandler, IHttpConnectionHand
public AniDBHttpConnectionHandler(ILoggerFactory loggerFactory, HttpRateLimiter rateLimiter) : base(loggerFactory, rateLimiter)
{
+ _httpClient = new HttpClient(new HttpClientHandler
+ {
+ AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate,
+ });
+ _httpClient.Timeout = TimeSpan.FromSeconds(20);
+ _httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
+ _httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("deflate"));
+ _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1");
}
public async Task> GetHttp(string url)
@@ -39,16 +48,7 @@ public async Task> GetHttpDirectly(string url)
RateLimiter.EnsureRate();
- var client = new HttpClient(new HttpClientHandler
- {
- AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate,
- });
- client.Timeout = TimeSpan.FromSeconds(20);
- client.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
- client.DefaultRequestHeaders.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("deflate"));
- client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1");
-
- using var response = await client.GetAsync(url);
+ using var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var responseStream = await response.Content.ReadAsStreamAsync();