From 766d2ee413a15c682c0d687619064caf98f9031c Mon Sep 17 00:00:00 2001 From: crobibero Date: Mon, 20 Apr 2020 14:21:06 -0600 Subject: [PATCH 1/8] Move RemoteImageService to Jellyfin.API --- .../Images/RemoteImageController.cs | 290 +++++++++++++++++ MediaBrowser.Api/Images/RemoteImageService.cs | 295 ------------------ 2 files changed, 290 insertions(+), 295 deletions(-) create mode 100644 Jellyfin.Api/Controllers/Images/RemoteImageController.cs delete mode 100644 MediaBrowser.Api/Images/RemoteImageService.cs diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs new file mode 100644 index 0000000000..66479582da --- /dev/null +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -0,0 +1,290 @@ +#nullable enable + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers.Images +{ + /// + /// Remote Images Controller. + /// + [Route("Images")] + [Authenticated] + public class RemoteImageController : BaseJellyfinApiController + { + private readonly IProviderManager _providerManager; + private readonly IServerApplicationPaths _applicationPaths; + private readonly IHttpClient _httpClient; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public RemoteImageController( + IProviderManager providerManager, + IServerApplicationPaths applicationPaths, + IHttpClient httpClient, + ILibraryManager libraryManager) + { + _providerManager = providerManager; + _applicationPaths = applicationPaths; + _httpClient = httpClient; + _libraryManager = libraryManager; + } + + /// + /// Gets available remote images for an item. + /// + /// Item Id. + /// The image type. + /// Optional. The record index to start at. All items with a lower index will be dropped from the results. + /// Optional. The maximum number of records to return. + /// Optional. The image provider to use. + /// Optinal. Include all languages. + /// Remote Image Result. + [HttpGet("{Id}/RemoteImages")] + [ProducesResponseType(typeof(RemoteImageResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] + public async Task GetRemoteImages( + [FromRoute] string id, + [FromQuery] ImageType? type, + [FromQuery] int? startIndex, + [FromQuery] int? limit, + [FromQuery] string providerName, + [FromQuery] bool includeAllLanguages) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery + { + ProviderName = providerName, + IncludeAllLanguages = includeAllLanguages, + IncludeDisabledProviders = true, + ImageType = type + }, CancellationToken.None) + .ConfigureAwait(false); + + var imageArray = images.ToArray(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + if (type.HasValue) + { + allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); + } + + var result = new RemoteImageResult + { + TotalRecordCount = imageArray.Length, + Providers = allProviders.Select(o => o.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; + + if (startIndex.HasValue) + { + imageArray = imageArray.Skip(startIndex.Value).ToArray(); + } + + if (limit.HasValue) + { + imageArray = imageArray.Take(limit.Value).ToArray(); + } + + result.Images = imageArray; + return Ok(result); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets available remote image providers for an item. + /// + /// Item Id. + /// List of providers. + [HttpGet("{Id}/RemoteImages/Providers")] + [ProducesResponseType(typeof(ImageProviderInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public IActionResult GetRemoteImageProviders([FromRoute] string id) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + var providers = _providerManager.GetRemoteImageProviderInfo(item); + return Ok(providers); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets a remote image. + /// + /// The image url. + /// Image Stream. + [HttpGet("Remote")] + [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) + { + try + { + var urlHash = imageUrl.GetMD5(); + var pointerCachePath = GetFullCachePath(urlHash.ToString()); + + string? contentPath = null; + bool hasFile = false; + + try + { + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + if (System.IO.File.Exists(contentPath)) + { + hasFile = true; + } + } + catch (FileNotFoundException) + { + // Means the file isn't cached yet + } + catch (IOException) + { + // Means the file isn't cached yet + } + + if (!hasFile) + { + await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + } + + if (string.IsNullOrEmpty(contentPath)) + { + return NotFound(); + } + + var contentType = MimeTypes.GetMimeType(contentPath); + return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Downloads a remote image for an item. + /// + /// Item Id. + /// The image type. + /// The image url. + /// Status. + [HttpPost("{Id}/RemoteImages/Download")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] + public async Task DownloadRemoteImage( + [FromRoute] string id, + [FromQuery, BindRequired] ImageType type, + [FromQuery] string imageUrl) + { + try + { + var item = _libraryManager.GetItemById(id); + if (item == null) + { + return NotFound(); + } + + await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) + .ConfigureAwait(false); + + item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); + return Ok(); + } + catch (Exception e) + { + return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + } + } + + /// + /// Gets the full cache path. + /// + /// The filename. + /// System.String. + private string GetFullCachePath(string filename) + { + return Path.Combine(_applicationPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); + } + + /// + /// Downloads the image. + /// + /// The URL. + /// The URL hash. + /// The pointer cache path. + /// Task. + private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) + { + using var result = await _httpClient.GetResponse(new HttpRequestOptions + { + Url = url, + BufferContent = false + }).ConfigureAwait(false); + var ext = result.ContentType.Split('/').Last(); + + var fullCachePath = GetFullCachePath(urlHash + "." + ext); + + Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); + using (var stream = result.Content) + { + using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(filestream).ConfigureAwait(false); + } + + Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); + await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None) + .ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs deleted file mode 100644 index 222bb34d31..0000000000 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ /dev/null @@ -1,295 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - public class BaseRemoteImageRequest : IReturn - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public ImageType? Type { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProviderName { get; set; } - - [ApiMember(Name = "IncludeAllLanguages", Description = "Optional.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeAllLanguages { get; set; } - } - - [Route("/Items/{Id}/RemoteImages", "GET", Summary = "Gets available remote images for an item")] - [Authenticated] - public class GetRemoteImages : BaseRemoteImageRequest - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Providers", "GET", Summary = "Gets available remote image providers for an item")] - [Authenticated] - public class GetRemoteImageProviders : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - public class BaseDownloadRemoteImage : IReturnVoid - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public ImageType Type { get; set; } - - [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ProviderName { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ImageUrl { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Download", "POST", Summary = "Downloads a remote image for an item")] - [Authenticated(Roles = "Admin")] - public class DownloadRemoteImage : BaseDownloadRemoteImage - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Images/Remote", "GET", Summary = "Gets a remote image")] - public class GetRemoteImage - { - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ImageUrl { get; set; } - } - - public class RemoteImageService : BaseApiService - { - private readonly IProviderManager _providerManager; - - private readonly IServerApplicationPaths _appPaths; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - - private readonly ILibraryManager _libraryManager; - - public RemoteImageService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - IServerApplicationPaths appPaths, - IHttpClient httpClient, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _appPaths = appPaths; - _httpClient = httpClient; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - } - - public object Get(GetRemoteImageProviders request) - { - var item = _libraryManager.GetItemById(request.Id); - - var result = GetImageProviders(item); - - return ToOptimizedResult(result); - } - - private List GetImageProviders(BaseItem item) - { - return _providerManager.GetRemoteImageProviderInfo(item).ToList(); - } - - public async Task Get(GetRemoteImages request) - { - var item = _libraryManager.GetItemById(request.Id); - - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery - { - ProviderName = request.ProviderName, - IncludeAllLanguages = request.IncludeAllLanguages, - IncludeDisabledProviders = true, - ImageType = request.Type - - }, CancellationToken.None).ConfigureAwait(false); - - var imagesList = images.ToArray(); - - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - - if (request.Type.HasValue) - { - allProviders = allProviders.Where(i => i.SupportedImages.Contains(request.Type.Value)); - } - - var result = new RemoteImageResult - { - TotalRecordCount = imagesList.Length, - Providers = allProviders.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; - - if (request.StartIndex.HasValue) - { - imagesList = imagesList.Skip(request.StartIndex.Value) - .ToArray(); - } - - if (request.Limit.HasValue) - { - imagesList = imagesList.Take(request.Limit.Value) - .ToArray(); - } - - result.Images = imagesList; - - return result; - } - - /// - /// Posts the specified request. - /// - /// The request. - public Task Post(DownloadRemoteImage request) - { - var item = _libraryManager.GetItemById(request.Id); - - return DownloadRemoteImage(item, request); - } - - /// - /// Downloads the remote image. - /// - /// The item. - /// The request. - /// Task. - private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) - { - await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false); - - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public async Task Get(GetRemoteImage request) - { - var urlHash = request.ImageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string contentPath; - - try - { - contentPath = File.ReadAllText(pointerCachePath); - - if (File.Exists(contentPath)) - { - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - - // Read the pointer file again - contentPath = File.ReadAllText(pointerCachePath); - - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - - /// - /// Downloads the image. - /// - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) - { - using var result = await _httpClient.GetResponse(new HttpRequestOptions - { - Url = url, - BufferContent = false - - }).ConfigureAwait(false); - var ext = result.ContentType.Split('/').Last(); - - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) - { - using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(filestream).ConfigureAwait(false); - } - - Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); - File.WriteAllText(pointerCachePath, fullCachePath); - } - - /// - /// Gets the full cache path. - /// - /// The filename. - /// System.String. - private string GetFullCachePath(string filename) - { - return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); - } - } -} From a6cd8526758386045a6895b0037f2199bdcb9003 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 07:58:54 -0600 Subject: [PATCH 2/8] Remove exception handler --- .../Images/RemoteImageController.cs | 184 ++++++++---------- 1 file changed, 78 insertions(+), 106 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 66479582da..8c7d21cd53 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -74,57 +74,50 @@ namespace Jellyfin.Api.Controllers.Images [FromQuery] string providerName, [FromQuery] bool includeAllLanguages) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } - - var images = await _providerManager.GetAvailableRemoteImages( - item, - new RemoteImageQuery - { - ProviderName = providerName, - IncludeAllLanguages = includeAllLanguages, - IncludeDisabledProviders = true, - ImageType = type - }, CancellationToken.None) - .ConfigureAwait(false); - - var imageArray = images.ToArray(); - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - if (type.HasValue) - { - allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); - } + return NotFound(); + } - var result = new RemoteImageResult - { - TotalRecordCount = imageArray.Length, - Providers = allProviders.Select(o => o.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; + var images = await _providerManager.GetAvailableRemoteImages( + item, + new RemoteImageQuery + { + ProviderName = providerName, + IncludeAllLanguages = includeAllLanguages, + IncludeDisabledProviders = true, + ImageType = type + }, CancellationToken.None) + .ConfigureAwait(false); - if (startIndex.HasValue) - { - imageArray = imageArray.Skip(startIndex.Value).ToArray(); - } + var imageArray = images.ToArray(); + var allProviders = _providerManager.GetRemoteImageProviderInfo(item); + if (type.HasValue) + { + allProviders = allProviders.Where(o => o.SupportedImages.Contains(type.Value)); + } - if (limit.HasValue) - { - imageArray = imageArray.Take(limit.Value).ToArray(); - } + var result = new RemoteImageResult + { + TotalRecordCount = imageArray.Length, + Providers = allProviders.Select(o => o.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray() + }; - result.Images = imageArray; - return Ok(result); + if (startIndex.HasValue) + { + imageArray = imageArray.Skip(startIndex.Value).ToArray(); } - catch (Exception e) + + if (limit.HasValue) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + imageArray = imageArray.Take(limit.Value).ToArray(); } + + result.Images = imageArray; + return Ok(result); } /// @@ -138,21 +131,14 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public IActionResult GetRemoteImageProviders([FromRoute] string id) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } - - var providers = _providerManager.GetRemoteImageProviderInfo(item); - return Ok(providers); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var providers = _providerManager.GetRemoteImageProviderInfo(item); + return Ok(providers); } /// @@ -166,49 +152,42 @@ namespace Jellyfin.Api.Controllers.Images [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) { - try - { - var urlHash = imageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); + var urlHash = imageUrl.GetMD5(); + var pointerCachePath = GetFullCachePath(urlHash.ToString()); - string? contentPath = null; - bool hasFile = false; + string? contentPath = null; + bool hasFile = false; - try - { - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - if (System.IO.File.Exists(contentPath)) - { - hasFile = true; - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - if (!hasFile) - { - await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); - } - - if (string.IsNullOrEmpty(contentPath)) + try + { + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); + if (System.IO.File.Exists(contentPath)) { - return NotFound(); + hasFile = true; } + } + catch (FileNotFoundException) + { + // Means the file isn't cached yet + } + catch (IOException) + { + // Means the file isn't cached yet + } - var contentType = MimeTypes.GetMimeType(contentPath); - return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); + if (!hasFile) + { + await DownloadImage(imageUrl, urlHash, pointerCachePath).ConfigureAwait(false); + contentPath = await System.IO.File.ReadAllTextAsync(pointerCachePath).ConfigureAwait(false); } - catch (Exception e) + + if (string.IsNullOrEmpty(contentPath)) { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); + return NotFound(); } + + var contentType = MimeTypes.GetMimeType(contentPath); + return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); } /// @@ -227,24 +206,17 @@ namespace Jellyfin.Api.Controllers.Images [FromQuery, BindRequired] ImageType type, [FromQuery] string imageUrl) { - try + var item = _libraryManager.GetItemById(id); + if (item == null) { - var item = _libraryManager.GetItemById(id); - if (item == null) - { - return NotFound(); - } + return NotFound(); + } - await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) - .ConfigureAwait(false); + await _providerManager.SaveImage(item, imageUrl, type, null, CancellationToken.None) + .ConfigureAwait(false); - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - return Ok(); - } - catch (Exception e) - { - return StatusCode(StatusCodes.Status500InternalServerError, e.Message); - } + item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); + return Ok(); } /// From 9ae895ba213a508f676d21e5425b25bb518ed89a Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 21 Apr 2020 14:09:06 -0600 Subject: [PATCH 3/8] move to ActionResult --- .../Images/RemoteImageController.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 8c7d21cd53..a0754ed4eb 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -63,10 +63,9 @@ namespace Jellyfin.Api.Controllers.Images /// Optinal. Include all languages. /// Remote Image Result. [HttpGet("{Id}/RemoteImages")] - [ProducesResponseType(typeof(RemoteImageResult), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)] - public async Task GetRemoteImages( + public async Task> GetRemoteImages( [FromRoute] string id, [FromQuery] ImageType? type, [FromQuery] int? startIndex, @@ -126,10 +125,9 @@ namespace Jellyfin.Api.Controllers.Images /// Item Id. /// List of providers. [HttpGet("{Id}/RemoteImages/Providers")] - [ProducesResponseType(typeof(ImageProviderInfo[]), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public IActionResult GetRemoteImageProviders([FromRoute] string id) + public ActionResult GetRemoteImageProviders([FromRoute] string id) { var item = _libraryManager.GetItemById(id); if (item == null) @@ -147,10 +145,10 @@ namespace Jellyfin.Api.Controllers.Images /// The image url. /// Image Stream. [HttpGet("Remote")] - [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] + [Produces("application/octet-stream")] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task GetRemoteImage([FromQuery, BindRequired] string imageUrl) + public async Task> GetRemoteImage([FromQuery, BindRequired] string imageUrl) { var urlHash = imageUrl.GetMD5(); var pointerCachePath = GetFullCachePath(urlHash.ToString()); @@ -200,8 +198,7 @@ namespace Jellyfin.Api.Controllers.Images [HttpPost("{Id}/RemoteImages/Download")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(string), StatusCodes.Status500InternalServerError)] - public async Task DownloadRemoteImage( + public async Task DownloadRemoteImage( [FromRoute] string id, [FromQuery, BindRequired] ImageType type, [FromQuery] string imageUrl) From 2923013c6ed0c5c4e7325893be0822d8fcd9de47 Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 19 May 2020 09:23:28 -0600 Subject: [PATCH 4/8] Clean Remote Image Controller. --- .../Images/RemoteImageController.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index a0754ed4eb..665db561bf 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -1,6 +1,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -9,12 +10,12 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; @@ -25,7 +26,7 @@ namespace Jellyfin.Api.Controllers.Images /// Remote Images Controller. /// [Route("Images")] - [Authenticated] + [Authorize] public class RemoteImageController : BaseJellyfinApiController { private readonly IProviderManager _providerManager; @@ -60,7 +61,9 @@ namespace Jellyfin.Api.Controllers.Images /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Optional. The image provider to use. - /// Optinal. Include all languages. + /// Optional. Include all languages. + /// Remote Images returned. + /// Item not found. /// Remote Image Result. [HttpGet("{Id}/RemoteImages")] [ProducesResponseType(StatusCodes.Status200OK)] @@ -116,18 +119,20 @@ namespace Jellyfin.Api.Controllers.Images } result.Images = imageArray; - return Ok(result); + return result; } /// /// Gets available remote image providers for an item. /// /// Item Id. - /// List of providers. + /// Returned remote image providers. + /// Item not found. + /// List of remote image providers. [HttpGet("{Id}/RemoteImages/Providers")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetRemoteImageProviders([FromRoute] string id) + public ActionResult> GetRemoteImageProviders([FromRoute] string id) { var item = _libraryManager.GetItemById(id); if (item == null) @@ -135,14 +140,15 @@ namespace Jellyfin.Api.Controllers.Images return NotFound(); } - var providers = _providerManager.GetRemoteImageProviderInfo(item); - return Ok(providers); + return Ok(_providerManager.GetRemoteImageProviderInfo(item)); } /// /// Gets a remote image. /// /// The image url. + /// Remote image returned. + /// Remote image not found. /// Image Stream. [HttpGet("Remote")] [Produces("application/octet-stream")] @@ -154,7 +160,7 @@ namespace Jellyfin.Api.Controllers.Images var pointerCachePath = GetFullCachePath(urlHash.ToString()); string? contentPath = null; - bool hasFile = false; + var hasFile = false; try { @@ -166,11 +172,11 @@ namespace Jellyfin.Api.Controllers.Images } catch (FileNotFoundException) { - // Means the file isn't cached yet + // The file isn't cached yet } catch (IOException) { - // Means the file isn't cached yet + // The file isn't cached yet } if (!hasFile) @@ -194,7 +200,9 @@ namespace Jellyfin.Api.Controllers.Images /// Item Id. /// The image type. /// The image url. - /// Status. + /// Remote image downloaded. + /// Remote image not found. + /// Download status. [HttpPost("{Id}/RemoteImages/Download")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -245,10 +253,10 @@ namespace Jellyfin.Api.Controllers.Images var fullCachePath = GetFullCachePath(urlHash + "." + ext); Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - using (var stream = result.Content) + await using (var stream = result.Content) { - using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await stream.CopyToAsync(filestream).ConfigureAwait(false); + await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); + await stream.CopyToAsync(fileStream).ConfigureAwait(false); } Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); From fb068b76a12bf9de5de75a0b8079effcd0336ecf Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 20 May 2020 07:18:51 -0600 Subject: [PATCH 5/8] Use correct MediaTypeName --- Jellyfin.Api/Controllers/Images/RemoteImageController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 665db561bf..1155cc653e 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Mime; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -151,7 +152,7 @@ namespace Jellyfin.Api.Controllers.Images /// Remote image not found. /// Image Stream. [HttpGet("Remote")] - [Produces("application/octet-stream")] + [Produces(MediaTypeNames.Application.Octet)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetRemoteImage([FromQuery, BindRequired] string imageUrl) From 552a74eb6e874976116447754785b6c1ca355718 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 13 Jun 2020 15:13:57 -0600 Subject: [PATCH 6/8] Fix build --- Jellyfin.Api/Controllers/Images/RemoteImageController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index 1155cc653e..f521dfdf28 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -85,9 +85,8 @@ namespace Jellyfin.Api.Controllers.Images var images = await _providerManager.GetAvailableRemoteImages( item, - new RemoteImageQuery + new RemoteImageQuery(providerName) { - ProviderName = providerName, IncludeAllLanguages = includeAllLanguages, IncludeDisabledProviders = true, ImageType = type From 3c18745f5392d45c1f008f15438e91831fb39294 Mon Sep 17 00:00:00 2001 From: crobibero Date: Sat, 13 Jun 2020 15:15:27 -0600 Subject: [PATCH 7/8] Remove RemoteImageService.cs --- MediaBrowser.Api/Images/RemoteImageService.cs | 297 ------------------ 1 file changed, 297 deletions(-) delete mode 100644 MediaBrowser.Api/Images/RemoteImageService.cs diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs deleted file mode 100644 index 358ac30fae..0000000000 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Api.Images -{ - public class BaseRemoteImageRequest : IReturn - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public ImageType? Type { get; set; } - - /// - /// Skips over a given number of items within the results. Use for paging. - /// - /// The start index. - [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? StartIndex { get; set; } - - /// - /// The maximum number of items to return - /// - /// The limit. - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - - [ApiMember(Name = "ProviderName", Description = "Optional. The image provider to use", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ProviderName { get; set; } - - [ApiMember(Name = "IncludeAllLanguages", Description = "Optional.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeAllLanguages { get; set; } - } - - [Route("/Items/{Id}/RemoteImages", "GET", Summary = "Gets available remote images for an item")] - [Authenticated] - public class GetRemoteImages : BaseRemoteImageRequest - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Providers", "GET", Summary = "Gets available remote image providers for an item")] - [Authenticated] - public class GetRemoteImageProviders : IReturn> - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - public class BaseDownloadRemoteImage : IReturnVoid - { - [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public ImageType Type { get; set; } - - [ApiMember(Name = "ProviderName", Description = "The image provider", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ProviderName { get; set; } - - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] - public string ImageUrl { get; set; } - } - - [Route("/Items/{Id}/RemoteImages/Download", "POST", Summary = "Downloads a remote image for an item")] - [Authenticated(Roles = "Admin")] - public class DownloadRemoteImage : BaseDownloadRemoteImage - { - /// - /// Gets or sets the id. - /// - /// The id. - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string Id { get; set; } - } - - [Route("/Images/Remote", "GET", Summary = "Gets a remote image")] - public class GetRemoteImage - { - [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public string ImageUrl { get; set; } - } - - public class RemoteImageService : BaseApiService - { - private readonly IProviderManager _providerManager; - - private readonly IServerApplicationPaths _appPaths; - private readonly IHttpClient _httpClient; - private readonly IFileSystem _fileSystem; - - private readonly ILibraryManager _libraryManager; - - public RemoteImageService( - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IHttpResultFactory httpResultFactory, - IProviderManager providerManager, - IServerApplicationPaths appPaths, - IHttpClient httpClient, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(logger, serverConfigurationManager, httpResultFactory) - { - _providerManager = providerManager; - _appPaths = appPaths; - _httpClient = httpClient; - _fileSystem = fileSystem; - _libraryManager = libraryManager; - } - - public object Get(GetRemoteImageProviders request) - { - var item = _libraryManager.GetItemById(request.Id); - - var result = GetImageProviders(item); - - return ToOptimizedResult(result); - } - - private List GetImageProviders(BaseItem item) - { - return _providerManager.GetRemoteImageProviderInfo(item).ToList(); - } - - public async Task Get(GetRemoteImages request) - { - var item = _libraryManager.GetItemById(request.Id); - - var images = await _providerManager.GetAvailableRemoteImages(item, new RemoteImageQuery(request.ProviderName) - { - IncludeAllLanguages = request.IncludeAllLanguages, - IncludeDisabledProviders = true, - ImageType = request.Type - - }, CancellationToken.None).ConfigureAwait(false); - - var imagesList = images.ToArray(); - - var allProviders = _providerManager.GetRemoteImageProviderInfo(item); - - if (request.Type.HasValue) - { - allProviders = allProviders.Where(i => i.SupportedImages.Contains(request.Type.Value)); - } - - var result = new RemoteImageResult - { - TotalRecordCount = imagesList.Length, - Providers = allProviders.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray() - }; - - if (request.StartIndex.HasValue) - { - imagesList = imagesList.Skip(request.StartIndex.Value) - .ToArray(); - } - - if (request.Limit.HasValue) - { - imagesList = imagesList.Take(request.Limit.Value) - .ToArray(); - } - - result.Images = imagesList; - - return result; - } - - /// - /// Posts the specified request. - /// - /// The request. - public Task Post(DownloadRemoteImage request) - { - var item = _libraryManager.GetItemById(request.Id); - - return DownloadRemoteImage(item, request); - } - - /// - /// Downloads the remote image. - /// - /// The item. - /// The request. - /// Task. - private async Task DownloadRemoteImage(BaseItem item, BaseDownloadRemoteImage request) - { - await _providerManager.SaveImage(item, request.ImageUrl, request.Type, null, CancellationToken.None).ConfigureAwait(false); - - item.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public async Task Get(GetRemoteImage request) - { - var urlHash = request.ImageUrl.GetMD5(); - var pointerCachePath = GetFullCachePath(urlHash.ToString()); - - string contentPath; - - try - { - contentPath = File.ReadAllText(pointerCachePath); - - if (File.Exists(contentPath)) - { - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - } - catch (FileNotFoundException) - { - // Means the file isn't cached yet - } - catch (IOException) - { - // Means the file isn't cached yet - } - - await DownloadImage(request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); - - // Read the pointer file again - contentPath = File.ReadAllText(pointerCachePath); - - return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); - } - - /// - /// Downloads the image. - /// - /// The URL. - /// The URL hash. - /// The pointer cache path. - /// Task. - private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath) - { - using var result = await _httpClient.GetResponse(new HttpRequestOptions - { - Url = url, - BufferContent = false - }).ConfigureAwait(false); - var ext = result.ContentType.Split('/')[^1]; - - var fullCachePath = GetFullCachePath(urlHash + "." + ext); - - Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); - var stream = result.Content; - await using (stream.ConfigureAwait(false)) - { - var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true); - await using (filestream.ConfigureAwait(false)) - { - await stream.CopyToAsync(filestream).ConfigureAwait(false); - } - } - - Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath)); - File.WriteAllText(pointerCachePath, fullCachePath); - } - - /// - /// Gets the full cache path. - /// - /// The filename. - /// System.String. - private string GetFullCachePath(string filename) - { - return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename); - } - } -} From 9b45ee440cd2d167aee63a05bcbb6137765b4da8 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 17 Jun 2020 10:51:50 -0600 Subject: [PATCH 8/8] User proper File constructor --- Jellyfin.Api/Controllers/Images/RemoteImageController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs index f521dfdf28..7c5f17e9e8 100644 --- a/Jellyfin.Api/Controllers/Images/RemoteImageController.cs +++ b/Jellyfin.Api/Controllers/Images/RemoteImageController.cs @@ -191,7 +191,7 @@ namespace Jellyfin.Api.Controllers.Images } var contentType = MimeTypes.GetMimeType(contentPath); - return new FileStreamResult(System.IO.File.OpenRead(contentPath), contentType); + return File(System.IO.File.OpenRead(contentPath), contentType); } ///