mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-30 18:22:48 -04:00 
			
		
		
		
	Merge pull request #3271 from crobibero/api-item-lookup
Move ItemLookupService to Jellyfin.Api
This commit is contained in:
		
						commit
						f736b43f97
					
				
							
								
								
									
										364
									
								
								Jellyfin.Api/Controllers/ItemLookupController.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								Jellyfin.Api/Controllers/ItemLookupController.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,364 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.ComponentModel.DataAnnotations; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Net.Mime; | ||||
| using System.Text.Json; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Jellyfin.Api.Constants; | ||||
| using MediaBrowser.Common.Extensions; | ||||
| using MediaBrowser.Controller; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using MediaBrowser.Controller.Entities; | ||||
| using MediaBrowser.Controller.Entities.Audio; | ||||
| using MediaBrowser.Controller.Entities.Movies; | ||||
| using MediaBrowser.Controller.Entities.TV; | ||||
| using MediaBrowser.Controller.Library; | ||||
| using MediaBrowser.Controller.Providers; | ||||
| using MediaBrowser.Model.IO; | ||||
| using MediaBrowser.Model.Providers; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
| using Microsoft.Extensions.Logging; | ||||
| 
 | ||||
| namespace Jellyfin.Api.Controllers | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Item lookup controller. | ||||
|     /// </summary> | ||||
|     [Authorize] | ||||
|     public class ItemLookupController : BaseJellyfinApiController | ||||
|     { | ||||
|         private readonly IProviderManager _providerManager; | ||||
|         private readonly IServerApplicationPaths _appPaths; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
|         private readonly ILibraryManager _libraryManager; | ||||
|         private readonly ILogger<ItemLookupController> _logger; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="ItemLookupController"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="providerManager">Instance of the <see cref="IProviderManager"/> interface.</param> | ||||
|         /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> | ||||
|         /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> | ||||
|         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> | ||||
|         /// <param name="logger">Instance of the <see cref="ILogger{ItemLookupController}"/> interface.</param> | ||||
|         public ItemLookupController( | ||||
|             IProviderManager providerManager, | ||||
|             IServerConfigurationManager serverConfigurationManager, | ||||
|             IFileSystem fileSystem, | ||||
|             ILibraryManager libraryManager, | ||||
|             ILogger<ItemLookupController> logger) | ||||
|         { | ||||
|             _providerManager = providerManager; | ||||
|             _appPaths = serverConfigurationManager.ApplicationPaths; | ||||
|             _fileSystem = fileSystem; | ||||
|             _libraryManager = libraryManager; | ||||
|             _logger = logger; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get the item's external id info. | ||||
|         /// </summary> | ||||
|         /// <param name="itemId">Item id.</param> | ||||
|         /// <response code="200">External id info retrieved.</response> | ||||
|         /// <response code="404">Item not found.</response> | ||||
|         /// <returns>List of external id info.</returns> | ||||
|         [HttpGet("/Items/{itemId}/ExternalIdInfos")] | ||||
|         [Authorize(Policy = Policies.RequiresElevation)] | ||||
|         [ProducesResponseType(StatusCodes.Status200OK)] | ||||
|         [ProducesResponseType(StatusCodes.Status404NotFound)] | ||||
|         public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute] Guid itemId) | ||||
|         { | ||||
|             var item = _libraryManager.GetItemById(itemId); | ||||
|             if (item == null) | ||||
|             { | ||||
|                 return NotFound(); | ||||
|             } | ||||
| 
 | ||||
|             return Ok(_providerManager.GetExternalIdInfos(item)); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get movie remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Movie remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Movie")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMovieRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MovieInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get trailer remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Trailer remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Trailer")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetTrailerRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<TrailerInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get music video remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Music video remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/MusicVideo")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicVideoRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<MusicVideoInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get series remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Series remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Series")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetSeriesRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<SeriesInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get box set remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Box set remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/BoxSet")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBoxSetRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BoxSetInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get music artist remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Music artist remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/MusicArtist")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicArtistRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<ArtistInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get music album remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Music album remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/MusicAlbum")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetMusicAlbumRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<AlbumInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get person remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Person remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Person")] | ||||
|         [Authorize(Policy = Policies.RequiresElevation)] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetPersonRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<PersonLookupInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             return Ok(results); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get book remote search. | ||||
|         /// </summary> | ||||
|         /// <param name="query">Remote search query.</param> | ||||
|         /// <response code="200">Book remote search executed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="OkResult"/> containing the list of remote search results. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Book")] | ||||
|         public async Task<ActionResult<IEnumerable<RemoteSearchResult>>> GetBookRemoteSearchResults([FromBody, BindRequired] RemoteSearchQuery<BookInfo> query) | ||||
|         { | ||||
|             var results = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(query, CancellationToken.None) | ||||
|                 .ConfigureAwait(false); | ||||
|             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")] | ||||
|         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)) | ||||
|                 { | ||||
|                     await using var fileStreamExisting = System.IO.File.OpenRead(pointerCachePath); | ||||
|                     return new FileStreamResult(fileStreamExisting, MediaTypeNames.Application.Octet); | ||||
|                 } | ||||
|             } | ||||
|             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); | ||||
| 
 | ||||
|             // Read the pointer file again | ||||
|             await using var fileStream = System.IO.File.OpenRead(pointerCachePath); | ||||
|             return new FileStreamResult(fileStream, MediaTypeNames.Application.Octet); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Applies search criteria to an item and refreshes metadata. | ||||
|         /// </summary> | ||||
|         /// <param name="itemId">Item id.</param> | ||||
|         /// <param name="searchResult">The remote search result.</param> | ||||
|         /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param> | ||||
|         /// <response code="204">Item metadata refreshed.</response> | ||||
|         /// <returns> | ||||
|         /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. | ||||
|         /// The task result contains an <see cref="NoContentResult"/>. | ||||
|         /// </returns> | ||||
|         [HttpPost("/Items/RemoteSearch/Apply/{id}")] | ||||
|         [Authorize(Policy = Policies.RequiresElevation)] | ||||
|         public async Task<ActionResult> ApplySearchCriteria( | ||||
|             [FromRoute] Guid itemId, | ||||
|             [FromBody, BindRequired] RemoteSearchResult searchResult, | ||||
|             [FromQuery] bool replaceAllImages = true) | ||||
|         { | ||||
|             var item = _libraryManager.GetItemById(itemId); | ||||
|             _logger.LogInformation( | ||||
|                 "Setting provider id's to item {0}-{1}: {2}", | ||||
|                 item.Id, | ||||
|                 item.Name, | ||||
|                 JsonSerializer.Serialize(searchResult.ProviderIds)); | ||||
| 
 | ||||
|             // Since the refresh process won't erase provider Ids, we need to set this explicitly now. | ||||
|             item.ProviderIds = searchResult.ProviderIds; | ||||
|             await _providerManager.RefreshFullItem( | ||||
|                 item, | ||||
|                 new MetadataRefreshOptions(new DirectoryService(_fileSystem)) | ||||
|                 { | ||||
|                     MetadataRefreshMode = MetadataRefreshMode.FullRefresh, | ||||
|                     ImageRefreshMode = MetadataRefreshMode.FullRefresh, | ||||
|                     ReplaceAllMetadata = true, | ||||
|                     ReplaceAllImages = replaceAllImages, | ||||
|                     SearchResult = searchResult | ||||
|                 }, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             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) | ||||
|         { | ||||
|             var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false); | ||||
|             var ext = result.ContentType.Split('/').Last(); | ||||
|             var fullCachePath = GetFullCachePath(urlHash + "." + ext); | ||||
| 
 | ||||
|             Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath)); | ||||
|             await using (var stream = result.Content) | ||||
|             { | ||||
|                 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)); | ||||
|             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); | ||||
|     } | ||||
| } | ||||
| @ -1,336 +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.Controller; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using MediaBrowser.Controller.Entities; | ||||
| using MediaBrowser.Controller.Entities.Audio; | ||||
| using MediaBrowser.Controller.Entities.Movies; | ||||
| using MediaBrowser.Controller.Entities.TV; | ||||
| using MediaBrowser.Controller.Library; | ||||
| using MediaBrowser.Controller.Net; | ||||
| using MediaBrowser.Controller.Providers; | ||||
| using MediaBrowser.Model.IO; | ||||
| using MediaBrowser.Model.Providers; | ||||
| using MediaBrowser.Model.Serialization; | ||||
| using MediaBrowser.Model.Services; | ||||
| using Microsoft.Extensions.Logging; | ||||
| 
 | ||||
| namespace MediaBrowser.Api | ||||
| { | ||||
|     [Route("/Items/{Id}/ExternalIdInfos", "GET", Summary = "Gets external id infos for an item")] | ||||
|     [Authenticated(Roles = "Admin")] | ||||
|     public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets or sets the id. | ||||
|         /// </summary> | ||||
|         /// <value>The id.</value> | ||||
|         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] | ||||
|         public Guid Id { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Movie", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Trailer", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/MusicVideo", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetMusicVideoRemoteSearchResults : RemoteSearchQuery<MusicVideoInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Series", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/BoxSet", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/MusicArtist", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetMusicArtistRemoteSearchResults : RemoteSearchQuery<ArtistInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/MusicAlbum", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetMusicAlbumRemoteSearchResults : RemoteSearchQuery<AlbumInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Person", "POST")] | ||||
|     [Authenticated(Roles = "Admin")] | ||||
|     public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Book", "POST")] | ||||
|     [Authenticated] | ||||
|     public class GetBookRemoteSearchResults : RemoteSearchQuery<BookInfo>, IReturn<List<RemoteSearchResult>> | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Image", "GET", Summary = "Gets a remote image")] | ||||
|     public class GetRemoteSearchImage | ||||
|     { | ||||
|         [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] | ||||
|         public string ImageUrl { get; set; } | ||||
| 
 | ||||
|         [ApiMember(Name = "ProviderName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] | ||||
|         public string ProviderName { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     [Route("/Items/RemoteSearch/Apply/{Id}", "POST", Summary = "Applies search criteria to an item and refreshes metadata")] | ||||
|     [Authenticated(Roles = "Admin")] | ||||
|     public class ApplySearchCriteria : RemoteSearchResult, IReturnVoid | ||||
|     { | ||||
|         [ApiMember(Name = "Id", Description = "The item id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] | ||||
|         public string Id { get; set; } | ||||
| 
 | ||||
|         [ApiMember(Name = "ReplaceAllImages", Description = "Whether or not to replace all images", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "POST")] | ||||
|         public bool ReplaceAllImages { get; set; } | ||||
| 
 | ||||
|         public ApplySearchCriteria() | ||||
|         { | ||||
|             ReplaceAllImages = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public class ItemLookupService : BaseApiService | ||||
|     { | ||||
|         private readonly IProviderManager _providerManager; | ||||
|         private readonly IServerApplicationPaths _appPaths; | ||||
|         private readonly IFileSystem _fileSystem; | ||||
|         private readonly ILibraryManager _libraryManager; | ||||
|         private readonly IJsonSerializer _json; | ||||
| 
 | ||||
|         public ItemLookupService( | ||||
|             ILogger<ItemLookupService> logger, | ||||
|             IServerConfigurationManager serverConfigurationManager, | ||||
|             IHttpResultFactory httpResultFactory, | ||||
|             IProviderManager providerManager, | ||||
|             IFileSystem fileSystem, | ||||
|             ILibraryManager libraryManager, | ||||
|             IJsonSerializer json) | ||||
|             : base(logger, serverConfigurationManager, httpResultFactory) | ||||
|         { | ||||
|             _providerManager = providerManager; | ||||
|             _appPaths = serverConfigurationManager.ApplicationPaths; | ||||
|             _fileSystem = fileSystem; | ||||
|             _libraryManager = libraryManager; | ||||
|             _json = json; | ||||
|         } | ||||
| 
 | ||||
|         public object Get(GetExternalIdInfos request) | ||||
|         { | ||||
|             var item = _libraryManager.GetItemById(request.Id); | ||||
| 
 | ||||
|             var infos = _providerManager.GetExternalIdInfos(item).ToList(); | ||||
| 
 | ||||
|             return ToOptimizedResult(infos); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetTrailerRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetBookRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<Book, BookInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetMovieRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetSeriesRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetBoxSetRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetMusicVideoRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<MusicVideo, MusicVideoInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetPersonRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetMusicAlbumRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public async Task<object> Post(GetMusicArtistRemoteSearchResults request) | ||||
|         { | ||||
|             var result = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).ConfigureAwait(false); | ||||
| 
 | ||||
|             return ToOptimizedResult(result); | ||||
|         } | ||||
| 
 | ||||
|         public Task<object> Get(GetRemoteSearchImage request) | ||||
|         { | ||||
|             return GetRemoteImage(request); | ||||
|         } | ||||
| 
 | ||||
|         public Task Post(ApplySearchCriteria request) | ||||
|         { | ||||
|             var item = _libraryManager.GetItemById(new Guid(request.Id)); | ||||
| 
 | ||||
|             //foreach (var key in request.ProviderIds) | ||||
|             //{ | ||||
|             //    var value = key.Value; | ||||
| 
 | ||||
|             //    if (!string.IsNullOrWhiteSpace(value)) | ||||
|             //    { | ||||
|             //        item.SetProviderId(key.Key, value); | ||||
|             //    } | ||||
|             //} | ||||
|             Logger.LogInformation("Setting provider id's to item {0}-{1}: {2}", item.Id, item.Name, _json.SerializeToString(request.ProviderIds)); | ||||
| 
 | ||||
|             // Since the refresh process won't erase provider Ids, we need to set this explicitly now. | ||||
|             item.ProviderIds = request.ProviderIds; | ||||
|             //item.ProductionYear = request.ProductionYear; | ||||
|             //item.Name = request.Name; | ||||
| 
 | ||||
|             return _providerManager.RefreshFullItem( | ||||
|                 item, | ||||
|                 new MetadataRefreshOptions(new DirectoryService(_fileSystem)) | ||||
|                 { | ||||
|                     MetadataRefreshMode = MetadataRefreshMode.FullRefresh, | ||||
|                     ImageRefreshMode = MetadataRefreshMode.FullRefresh, | ||||
|                     ReplaceAllMetadata = true, | ||||
|                     ReplaceAllImages = request.ReplaceAllImages, | ||||
|                     SearchResult = request | ||||
|                 }, | ||||
|                 CancellationToken.None); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Gets the remote image. | ||||
|         /// </summary> | ||||
|         /// <param name="request">The request.</param> | ||||
|         /// <returns>Task{System.Object}.</returns> | ||||
|         private async Task<object> GetRemoteImage(GetRemoteSearchImage 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.ProviderName, request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false); | ||||
| 
 | ||||
|             // Read the pointer file again | ||||
|             contentPath = File.ReadAllText(pointerCachePath); | ||||
| 
 | ||||
|             return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); | ||||
|         } | ||||
| 
 | ||||
|         /// <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) | ||||
|         { | ||||
|             var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).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); | ||||
|         } | ||||
| 
 | ||||
|         /// <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); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user