mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-31 02:27:18 -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