using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Implementations.HttpServer; using MediaBrowser.Common.Net; using MediaBrowser.Controller; 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")] public class GetItemImage : ImageRequest { /// /// Gets or sets the id. /// /// The id. public string Id { get; set; } } /// /// Class GetPersonImage /// [Route("/Persons/{Name}/Images/{Type}", "GET")] [Route("/Persons/{Name}/Images/{Type}/{Index}", "GET")] public class GetPersonImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } } /// /// Class GetStudioImage /// [Route("/Studios/{Name}/Images/{Type}", "GET")] [Route("/Studios/{Name}/Images/{Type}/{Index}", "GET")] public class GetStudioImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } } /// /// Class GetGenreImage /// [Route("/Genres/{Name}/Images/{Type}", "GET")] [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")] public class GetGenreImage : ImageRequest { /// /// Gets or sets the name. /// /// The name. public string Name { get; set; } } /// /// Class GetYearImage /// [Route("/Years/{Year}/Images/{Type}", "GET")] [Route("/Years/{Year}/Images/{Type}/{Index}", "GET")] public class GetYearImage : ImageRequest { /// /// Gets or sets the year. /// /// The year. public int Year { get; set; } } /// /// Class GetUserImage /// [Route("/Users/{Id}/Images/{Type}", "GET")] [Route("/Users/{Id}/Images/{Type}/{Index}", "GET")] public class GetUserImage : ImageRequest { /// /// Gets or sets the id. /// /// The id. public Guid Id { get; set; } } /// /// Class DeleteUserImage /// [Route("/Users/{Id}/Images/{Type}", "DELETE")] [Route("/Users/{Id}/Images/{Type}/{Index}", "DELETE")] public class DeleteUserImage : DeleteImageRequest, IReturnVoid { /// /// Gets or sets the id. /// /// The id. public Guid Id { get; set; } } [Route("/Users/{Id}/Images/{Type}", "POST")] [Route("/Users/{Id}/Images/{Type}/{Index}", "POST")] public class PostUserImage : DeleteImageRequest, IRequiresRequestStream, IReturnVoid { /// /// Gets or sets the id. /// /// The id. public Guid Id { get; set; } /// /// The raw Http Request Input Stream /// /// The request stream. public Stream RequestStream { get; set; } } /// /// Class ImageService /// public class ImageService : BaseRestService { /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetItemImage request) { var item = DtoBuilder.GetItemByClientId(request.Id); return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetUserImage request) { var kernel = (Kernel)Kernel; var item = kernel.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 kernel = (Kernel)Kernel; var item = kernel.LibraryManager.GetYear(request.Year).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetStudioImage request) { var kernel = (Kernel)Kernel; var item = kernel.LibraryManager.GetStudio(request.Name).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetPersonImage request) { var kernel = (Kernel)Kernel; var item = kernel.LibraryManager.GetPerson(request.Name).Result; return GetImage(request, item); } /// /// Gets the specified request. /// /// The request. /// System.Object. public object Get(GetGenreImage request) { var kernel = (Kernel)Kernel; var item = kernel.LibraryManager.GetGenre(request.Name).Result; return GetImage(request, item); } /// /// Posts the specified request. /// /// The request. public void Post(PostUserImage request) { var kernel = (Kernel)Kernel; var pathInfo = PathInfo.Parse(Request.PathInfo); var id = new Guid(pathInfo.GetArgumentValue(1)); request.Type = (ImageType)Enum.Parse(typeof(ImageType), pathInfo.GetArgumentValue(3), true); var item = kernel.Users.First(i => i.Id == id); var task = PostImage(item, request.RequestStream, request.Type, Request.ContentType); Task.WaitAll(task); } /// /// Deletes the specified request. /// /// The request. public void Delete(DeleteUserImage request) { var kernel = (Kernel)Kernel; var item = kernel.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)Kernel; var index = request.Index ?? 0; var imagePath = GetImagePath(kernel, request, item); if (string.IsNullOrEmpty(imagePath)) { throw new ResourceNotFoundException(); } // 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 = contentType }, 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.Substring(mimeType.IndexOf('/') + 1); 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); } } } }