|  | @@ -237,48 +237,6 @@ namespace Jellyfin.Api.Controllers
 | 
	
		
			
				|  |  |              return Ok(results);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets a remote image.
 | 
	
		
			
				|  |  | -        /// </summary>
 | 
	
		
			
				|  |  | -        /// <param name="imageUrl">The image url.</param>
 | 
	
		
			
				|  |  | -        /// <param name="providerName">The provider name.</param>
 | 
	
		
			
				|  |  | -        /// <response code="200">Remote image retrieved.</response>
 | 
	
		
			
				|  |  | -        /// <returns>
 | 
	
		
			
				|  |  | -        /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
 | 
	
		
			
				|  |  | -        /// The task result contains an <see cref="FileStreamResult"/> containing the images file stream.
 | 
	
		
			
				|  |  | -        /// </returns>
 | 
	
		
			
				|  |  | -        [HttpGet("Items/RemoteSearch/Image")]
 | 
	
		
			
				|  |  | -        [ProducesResponseType(StatusCodes.Status200OK)]
 | 
	
		
			
				|  |  | -        [ProducesImageFile]
 | 
	
		
			
				|  |  | -        public async Task<ActionResult> GetRemoteSearchImage(
 | 
	
		
			
				|  |  | -            [FromQuery, Required] string imageUrl,
 | 
	
		
			
				|  |  | -            [FromQuery, Required] string providerName)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            var urlHash = imageUrl.GetMD5();
 | 
	
		
			
				|  |  | -            var pointerCachePath = GetFullCachePath(urlHash.ToString());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            try
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                var contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -                if (System.IO.File.Exists(contentPath))
 | 
	
		
			
				|  |  | -                {
 | 
	
		
			
				|  |  | -                    return PhysicalFile(contentPath, MimeTypes.GetMimeType(contentPath));
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            catch (FileNotFoundException)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // Means the file isn't cached yet
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -            catch (IOException)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // Means the file isn't cached yet
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            await DownloadImage(providerName, imageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -            var updatedContentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -            return PhysicalFile(updatedContentPath, MimeTypes.GetMimeType(updatedContentPath));
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Applies search criteria to an item and refreshes metadata.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
	
		
			
				|  | @@ -320,54 +278,5 @@ namespace Jellyfin.Api.Controllers
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return NoContent();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        /// <summary>
 | 
	
		
			
				|  |  | -        /// Downloads the image.
 | 
	
		
			
				|  |  | -        /// </summary>
 | 
	
		
			
				|  |  | -        /// <param name="providerName">Name of the provider.</param>
 | 
	
		
			
				|  |  | -        /// <param name="url">The URL.</param>
 | 
	
		
			
				|  |  | -        /// <param name="urlHash">The URL hash.</param>
 | 
	
		
			
				|  |  | -        /// <param name="pointerCachePath">The pointer cache path.</param>
 | 
	
		
			
				|  |  | -        /// <returns>Task.</returns>
 | 
	
		
			
				|  |  | -        private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            using var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -            if (result.Content.Headers.ContentType?.MediaType == null)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                throw new ResourceNotFoundException(nameof(result.Content.Headers.ContentType));
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            var ext = result.Content.Headers.ContentType.MediaType.Split('/')[^1];
 | 
	
		
			
				|  |  | -            var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            var directory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
 | 
	
		
			
				|  |  | -            Directory.CreateDirectory(directory);
 | 
	
		
			
				|  |  | -            using (var stream = result.Content)
 | 
	
		
			
				|  |  | -            {
 | 
	
		
			
				|  |  | -                // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
 | 
	
		
			
				|  |  | -                await using var fileStream = new FileStream(
 | 
	
		
			
				|  |  | -                    fullCachePath,
 | 
	
		
			
				|  |  | -                    FileMode.Create,
 | 
	
		
			
				|  |  | -                    FileAccess.Write,
 | 
	
		
			
				|  |  | -                    FileShare.None,
 | 
	
		
			
				|  |  | -                    IODefaults.FileStreamBufferSize,
 | 
	
		
			
				|  |  | -                    true);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -                await stream.CopyToAsync(fileStream).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            Directory.CreateDirectory(pointerCacheDirectory);
 | 
	
		
			
				|  |  | -            await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath).ConfigureAwait(false);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the full cache path.
 | 
	
		
			
				|  |  | -        /// </summary>
 | 
	
		
			
				|  |  | -        /// <param name="filename">The filename.</param>
 | 
	
		
			
				|  |  | -        /// <returns>System.String.</returns>
 | 
	
		
			
				|  |  | -        private string GetFullCachePath(string filename)
 | 
	
		
			
				|  |  | -            => Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |