protected string GetPath(long id, GetFileModify modify = null)//int size = 0) { var name = id.ToString(); if (modify != null) { var extraFolder = "_"; if (modify.size > 0) { extraFolder += $"{modify.size}"; } if (modify.crop) { extraFolder += "a"; } if (extraFolder != "_") { return(Path.Join(config.Location, extraFolder, name)); } } return(Path.Join(config.Location, name)); }
public async Task <byte[]> GetThumbnailAsync(string hash, string thumbnailPath, GetFileModify modify) { //NOTE: locking on the entire write may increase wait times for brand new images (and image uploads) but it saves cpu cycles //in those cases by only resizing an image once. await filelock.WaitAsync(); try { //Will checking the fileinfo be too much?? if (!System.IO.File.Exists(thumbnailPath) || (new FileInfo(thumbnailPath)).Length == 0) { Directory.CreateDirectory(Path.GetDirectoryName(thumbnailPath) ?? throw new InvalidOperationException("No parent for thumbail path?")); using (var memStream = new MemoryStream(await GetMainDataAsync(hash))) { var manipResult = await imageManip.MakeThumbnailAndSave(memStream, thumbnailPath, modify); AddImageRender(manipResult.RenderCount + manipResult.LoadCount); } } } finally { filelock.Release(); } return(await System.IO.File.ReadAllBytesAsync(thumbnailPath)); }
protected string GetAndMakePath(long id, GetFileModify modify = null) { var result = GetPath(id, modify); System.IO.Directory.CreateDirectory(Path.GetDirectoryName(result)); return(result); }
public async Task <ActionResult <bool> > GetFileAsync([FromRoute] string hash, [FromQuery] GetFileModify modify) { //This is fine because files contents will never change, BUT isn't appropriate for most other things! Response.Headers.Add("ETag", hash); try { var result = await service.GetFileAsync(hash, modify); return(File(result.Item1, result.Item2)); } catch (Exception ex) { return(await MatchExceptions(() => { ExceptionDispatchInfo.Capture(ex).Throw(); return Task.FromResult(true); })); } }
public async Task <Tuple <byte[], string> > GetFileAsync(string hash, GetFileModify modify) { if (modify.size > 0) { if (modify.size < 10) { throw new RequestException("Requested file size too small!"); } else if (modify.size <= 100) { modify.size = 10 * (modify.size / 10); } else if (modify.size <= 1000) { modify.size = 100 * (modify.size / 100); } else { throw new RequestException("Requested file size too large!"); } } //Go get that ONE file. This should return null if we can't read it... let's hope! //SPECIAL: the 0 file is ContentView?fileData = null; if (hash == Constants.DefaultHash) { fileData = new ContentView() { id = 0, literalType = FallbackMime, hash = hash, contentType = InternalContentType.file }; } else { //Doesn't matter who the requester is, ANY file with this hash is fine... what about deleted though? using var searcher = dbFactory.CreateSearch(); fileData = (await searcher.GetByField <ContentView>(RequestType.content, "hash", hash)).FirstOrDefault(); } if (fileData == null || fileData.deleted || fileData.contentType != InternalContentType.file) { throw new NotFoundException($"Couldn't find file data with hash {hash}"); } var thumbnailPath = GetThumbnailPath(hash, modify); var mimeType = fileData.literalType ?? ""; try { //This means they're requesting a MAIN data, just go get it if (string.IsNullOrEmpty(thumbnailPath)) { return(Tuple.Create(await GetMainDataAsync(hash), mimeType)); } else { return(Tuple.Create(await GetThumbnailAsync(hash, thumbnailPath, modify), mimeType)); } } catch (NotFoundException) { //This just means we CAN generate it! if (hash == Constants.DefaultHash && !string.IsNullOrWhiteSpace(config.DefaultImageFallback)) { logger.LogInformation($"Creating default image {hash} from base64 string given in config"); await SaveMainDataAsync(Convert.FromBase64String(config.DefaultImageFallback), hash, mimeType); return(await GetFileAsync(hash, modify)); } else { throw; } } }
[ResponseCache(Duration = 13824000)] //six months public async Task <IActionResult> GetFileAsync([FromRoute] long id, [FromQuery] GetFileModify modify) { if (modify.size > 0) { if (modify.size < 10) { return(BadRequest("Too small!")); } else if (modify.size <= 100) { modify.size = 10 * (modify.size / 10); } else if (modify.size <= 1000) { modify.size = 100 * (modify.size / 100); } else { return(BadRequest("Too large!")); } } var requester = GetRequesterNoFail(); //Go get that ONE file. This should return null if we can't read it... let's hope! //SPECIAL: the 0 file is FileView fileData = null; if (id == 0) { if (System.IO.File.Exists(GetAndMakePath(0))) { fileData = new FileView() { id = 0, fileType = "image/png" } } ; } else { fileData = await service.FindByIdAsync(id, requester); } if (fileData == null) { return(NotFound()); } var finalPath = GetAndMakePath(fileData.id, modify); //Ok NOW we can go get it. We may need to perform a resize beforehand if we can't find the file. if (!System.IO.File.Exists(finalPath)) { var baseImage = GetPath(fileData.id); IImageFormat format; await Task.Run(() => { using (var image = Image.Load(baseImage, out format)) { var maxDim = Math.Max(image.Width, image.Height); var minDim = Math.Min(image.Width, image.Height); //Square ALWAYS happens, it can happen before other things. if (modify.crop) { image.Mutate(x => x.Crop(new Rectangle((image.Width - minDim) / 2, (image.Height - minDim) / 2, minDim, minDim))); } if (modify.size > 0 && !((format.DefaultMimeType == "image/gif" /*|| modify.noGrow*/) && (modify.size > image.Width || modify.size > image.Height))) { var width = 0; var height = 0; //Preserve aspect ratio when not square if (image.Width > image.Height) { width = modify.size; } else { height = modify.size; } image.Mutate(x => x.Resize(width, height)); } using (var stream = System.IO.File.OpenWrite(finalPath)) { image.Save(stream, format); } } }); } Response.Headers.Add("ETag", GetETag(finalPath)); return(File(System.IO.File.OpenRead(finalPath), fileData.fileType)); }
[ResponseCache(Duration = 13824000)] //six months public async Task <IActionResult> GetFileAsync([FromRoute] long id, [FromQuery] GetFileModify modify) { if (modify.size > 0) { if (modify.size < 10) { return(BadRequest("Too small!")); } else if (modify.size <= 100) { modify.size = 10 * (modify.size / 10); } else if (modify.size <= 1000) { modify.size = 100 * (modify.size / 100); } else { return(BadRequest("Too large!")); } } var requester = GetRequesterNoFail(); //Go get that ONE file. This should return null if we can't read it... let's hope! //SPECIAL: the 0 file is FileView fileData = null; if (id == 0) { if (System.IO.File.Exists(GetAndMakePath(0))) { fileData = new FileView() { id = 0, fileType = "image/png" } } ; } else { fileData = (await service.SearchAsync(new FileSearch() { Ids = new List <long>() { id }, SearchAllBuckets = true }, requester)).OnlySingle(); //FindByIdAsync(id, requester); } if (fileData == null) { return(NotFound()); } var finalPath = GetAndMakePath(fileData.id, modify); //Ok NOW we can go get it. We may need to perform a resize beforehand if we can't find the file. //NOTE: locking on the entire write may increase wait times for brand new images (and image uploads) but it saves cpu cycles //in those cases by only resizing an image once. await filelock.WaitAsync(); try { //Will checking the fileinfo be too much?? if (!System.IO.File.Exists(finalPath) || (new FileInfo(finalPath)).Length == 0) { var baseImage = GetPath(fileData.id); IImageFormat format; await Task.Run(() => { using (var image = Image.Load(baseImage, out format)) { var maxDim = Math.Max(image.Width, image.Height); var minDim = Math.Min(image.Width, image.Height); var isGif = format.DefaultMimeType == "image/gif"; //Square ALWAYS happens, it can happen before other things. if (modify.crop) { image.Mutate(x => x.Crop(new Rectangle((image.Width - minDim) / 2, (image.Height - minDim) / 2, minDim, minDim))); } //Saving as png also works, but this preserves the format (even if it's a little heavier compute, it's only a one time thing) if (modify.freeze && isGif) { while (image.Frames.Count > 1) { image.Frames.RemoveFrame(1); } } if (modify.size > 0 && !(isGif && (modify.size > image.Width || modify.size > image.Height))) { var width = 0; var height = 0; //Preserve aspect ratio when not square if (image.Width > image.Height) { width = modify.size; } else { height = modify.size; } image.Mutate(x => x.Resize(width, height)); } using (var stream = System.IO.File.OpenWrite(finalPath)) { image.Save(stream, format); } } }); } } finally { filelock.Release(); } Response.Headers.Add("ETag", GetETag(finalPath)); return(File(System.IO.File.OpenRead(finalPath), fileData.fileType)); }
/// <summary> /// Given an image as a stream, perform the given modifications to it and save it to the given path /// </summary> /// <param name="fileData"></param> /// <param name="savePath"></param> /// <param name="modify"></param> /// <returns></returns> public async Task <ImageManipulationInfo> MakeThumbnailAndSave(Stream fileData, string savePath, GetFileModify modify) //, bool highQualityResize) { var result = new ImageManipulationInfo { RenderCount = 0, LoadCount = 1 }; await SingleManipLock.WaitAsync(); try { await Task.Run(() => { IImageFormat?format; using var image = Image.Load(fileData, out format); result.MimeType = format.DefaultMimeType; //var maxDim = Math.Max(image.Width, image.Height); var isGif = format.DefaultMimeType == GifMime; var isJpg = format.DefaultMimeType == JpegMime; //Square ALWAYS happens, it can happen before other things. if (modify.crop) { var minDim = Math.Min(image.Width, image.Height); image.Mutate(x => x.Crop(new Rectangle((image.Width - minDim) / 2, (image.Height - minDim) / 2, minDim, minDim))); result.RenderCount++; } //This must come after the crop! var isNowLarger = (modify.size > Math.Max(image.Width, image.Height)); //Saving as png also works, but this preserves the format (even if it's a little heavier compute, it's only a one time thing) if (modify.freeze && isGif) { while (image.Frames.Count > 1) { image.Frames.RemoveFrame(1); } result.RenderCount++; } if (modify.size > 0 && !(isGif && isNowLarger)) //&& (modify.size > image.Width || modify.size > image.Height))) { var width = 0; var height = 0; //Preserve aspect ratio when not square if (image.Width > image.Height) { width = modify.size; } else { height = modify.size; } if (HighQualityResize) { image.Mutate(x => x.Resize(width, height, isNowLarger ? KnownResamplers.Spline : KnownResamplers.Lanczos3)); } else { image.Mutate(x => x.Resize(width, height)); } result.RenderCount++; } result.Width = image.Width; result.Height = image.Height; using (var stream = System.IO.File.OpenWrite(savePath)) { IImageEncoder?encoder = null; if (HighQualityResize && modify.size <= MinJpegHighQualitySize && isJpg) { encoder = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder() { Quality = JpegHighQuality, }; } if (encoder != null) { image.Save(stream, encoder); } else { image.Save(stream, format); } result.SizeInBytes = stream.Length; } }); } finally { SingleManipLock.Release(); } return(result); }
public Task <ImageManipulationInfo> MakeThumbnailAndSave(Stream fileData, string savePath, GetFileModify modify) { return(SelfRunSystem.RunProcessWithFileAsync <ImageManipulationInfo>(fileData, SelfRunSystem.RunImageThumbnail, new ImageManipulationMakeThumbnailArgument { modify = modify, savePath = Path.GetFullPath(savePath) })); }