using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using ServiceStack.ServiceHost; using ServiceStack.Text.Controller; using System; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api.Images { /// /// Class GetItemImage /// [Route("/Items/{Id}/Images/{Type}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets an item image")] public class GetItemImage : ImageRequest { /// /// 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; } } /// /// Class GetPersonImage /// [Route("/Persons/{Name}/Images/{Type}", "GET")] [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets a person image")] public class GetPersonImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. [ApiMember(Name = "Name", Description = "Person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } /// /// Class GetStudioImage /// [Route("/Studios/{Name}/Images/{Type}", "GET")] [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets a studio image")] public class GetStudioImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. [ApiMember(Name = "Name", Description = "Studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } /// /// Class GetGenreImage /// [Route("/Genres/{Name}/Images/{Type}", "GET")] [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets a genre image")] public class GetGenreImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. [ApiMember(Name = "Name", Description = "Genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } /// /// Class GetYearImage /// [Route("/Years/{Year}/Images/{Type}", "GET")] [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets a year image")] public class GetYearImage : ImageRequest { /// /// Gets or sets the year. /// /// The year. [ApiMember(Name = "Year", Description = "Year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] public int Year { get; set; } } /// /// Class GetUserImage /// [Route("/Users/{Id}/Images/{Type}", "GET")] [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")] [Api(Description = "Gets a user image")] public class GetUserImage : ImageRequest { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public Guid Id { get; set; } } /// /// Class DeleteUserImage /// [Route("/Users/{Id}/Images/{Type}", "DELETE")] [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] [Api(Description = "Deletes a user image")] public class DeleteUserImage : DeleteImageRequest, IReturnVoid { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public Guid Id { get; set; } } [Route("/Users/{Id}/Images/{Type}", "POST")] [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] [Api(Description = "Posts a user image")] public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// /// Gets or sets the id. /// /// The id. [ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public Guid Id { get; set; } /// /// The raw Http Request Input Stream /// /// The request stream. public Stream RequestStream { get; set; } } /// /// Class ImageService /// public class ImageService : BaseApiService { /// /// The _user manager /// private readonly IUserManager _userManager; /// /// The _library manager /// private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. /// /// The user manager. /// The library manager. public ImageService(IUserManager userManager, ILibraryManager libraryManager) { _userManager = userManager; _libraryManager = libraryManager; } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetItemImage request) { var item = string.IsNullOrEmpty(request.Id) ? _libraryManager.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager); return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetUserImage request) { var item = _userManager.Users.First(i => i.Id == request.Id); return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetYearImage request) { var item = _libraryManager.GetYear(request.Year).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetStudioImage request) { var item = _libraryManager.GetStudio(request.Name).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetPersonImage request) { var item = _libraryManager.GetPerson(request.Name).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetGenreImage request) { var item = _libraryManager.GetGenre(request.Name).Result; return GetImage(request, item); } /// /// Posts the specified request. /// /// The request. public void Post(PostUserImage request) { var pathInfo = PathInfo.Parse(RequestContext.PathInfo); var id = new Guid(pathInfo.GetArgumentValue(1)); request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue(3), true); var item = _userManager.Users.First(i => i.Id == id); var task = PostImage(item, request.RequestStream, request.Type, RequestContext.ContentType); Task.WaitAll(task); } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteUserImage request) { var item = _userManager.Users.First(i => i.Id == request.Id); var task = item.DeleteImage(request.Type); Task.WaitAll(task); } /// /// Gets the image. /// /// The request. /// The item. /// System.Object. /// private object GetImage(ImageRequest request, BaseItem item) { var kernel = Kernel.Instance; var index = request.Index ?? 0; var imagePath = GetImagePath(kernel, request, item); if (string.IsNullOrEmpty(imagePath)) { throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); } // See if we can avoid a file system lookup by looking for the file in ResolveArgs var originalFileImageDateModified = kernel.ImageManager.GetImageDateModified(item, request.Type, index); var supportedImageEnhancers = kernel.ImageEnhancers.Where(i => i.Supports(item, request.Type)).ToList(); // If the file does not exist GetLastWriteTimeUtc will return jan 1, 1601 as opposed to throwing an exception // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx if (originalFileImageDateModified.Year == 1601 && !File.Exists(imagePath)) { throw new ResourceNotFoundException(string.Format("File not found: {0}", imagePath)); } var contentType = MimeTypes.GetMimeType(imagePath); var dateLastModified = (supportedImageEnhancers.Select(e => e.LastConfigurationChange(item, request.Type)).Concat(new[] { originalFileImageDateModified })).Max(); var cacheGuid = kernel.ImageManager.GetImageCacheTag(imagePath, originalFileImageDateModified, supportedImageEnhancers, item, request.Type); TimeSpan? cacheDuration = null; if (!string.IsNullOrEmpty(request.Tag) && cacheGuid == new Guid(request.Tag)) { cacheDuration = TimeSpan.FromDays(365); } return ToCachedResult(cacheGuid, dateLastModified, cacheDuration, () => new ImageWriter { Item = item, Request = request, CropWhiteSpace = request.Type == ImageType.Logo || request.Type == ImageType.Art, OriginalImageDateModified = originalFileImageDateModified }, contentType); } /// /// Gets the image path. /// /// The kernel. /// The request. /// The item. /// System.String. private string GetImagePath(Kernel kernel, ImageRequest request, BaseItem item) { var index = request.Index ?? 0; return kernel.ImageManager.GetImagePath(item, request.Type, index); } /// /// Posts the image. /// /// The entity. /// The input stream. /// Type of the image. /// Type of the MIME. /// Task. private async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType) { using (var reader = new StreamReader(inputStream)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); var bytes = Convert.FromBase64String(text); string filename; switch (imageType) { case ImageType.Art: filename = "clearart"; break; case ImageType.Primary: filename = "folder"; break; default: filename = imageType.ToString().ToLower(); break; } var extension = mimeType.Split(';').First().Split('/').Last(); var oldImagePath = entity.GetImage(imageType); var imagePath = Path.Combine(entity.MetaLocation, filename + "." + extension); // Save to file system using (var fs = new FileStream(imagePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true)) { await fs.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } // Set the image entity.SetImage(imageType, imagePath); // If the new and old paths are different, delete the old one if (!string.IsNullOrEmpty(oldImagePath) && !oldImagePath.Equals(imagePath, StringComparison.OrdinalIgnoreCase)) { File.Delete(oldImagePath); } // Directory watchers should repeat this, but do a quick refresh first await entity.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false).ConfigureAwait(false); } } } }