diff options
| -rw-r--r-- | Controllers/MediaController.cs | 40 | ||||
| -rw-r--r-- | Exception.cs | 14 | ||||
| -rw-r--r-- | Pages/Gallery.razor | 5 | ||||
| -rw-r--r-- | Services/MediaService.cs | 105 | ||||
| -rw-r--r-- | wwwroot/styles/global.css | 4 |
5 files changed, 116 insertions, 52 deletions
diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs index 674b406..85dfc65 100644 --- a/Controllers/MediaController.cs +++ b/Controllers/MediaController.cs @@ -38,39 +38,17 @@ public class MediaController : Controller { [HttpGet("thumb/{mediaId}")] public IActionResult Thumbnail( [FromRoute] Guid mediaId, - [FromQuery] int? w, - [FromQuery] int? h) { + [FromQuery(Name = "w")] int? width, + [FromQuery(Name = "h")] int? height) { - var media = db.Media.First(m => m.Guid == mediaId); - if(media is null) - return NotFound(); - - if(media.MimeType.Split("/")[0] != "image") - return BadRequest("Media object not an image"); - - using var image = new MagickImage(mediaService.GetPath(media)); - - if(w is null && h is null) - return BadRequest("Both width and height cannot be null!"); - - if(w > image.Width || h > image.Height) - return BadRequest("Requested thumbnail size is larger than original media"); - - #pragma warning disable CS8629 - int width = (int)(w is not null ? w : image.Width * h / image.Height); - int height = (int)(h is not null ? h : image.Height * w / image.Width); - #pragma warning restore CS8629 - - var thumbPath = mediaService.GetPath(media, width, height); - - if(!System.IO.File.Exists(thumbPath)) { - image.Resize(width, height); - image.Write(thumbPath); + try { + var thumb = mediaService.GetThumbnail(mediaId, width, height); + return new FileStreamResult(thumb, "image/jpeg"); + } catch(ThumbnailException e) { + return BadRequest(e.Message); + } catch(ObjectNotFoundException e) { + return NotFound(e.Message); } - - var fs = System.IO.File.OpenRead(thumbPath); - - return new FileStreamResult(fs, "image/jpeg"); } [HttpDelete("{mediaId}")] diff --git a/Exception.cs b/Exception.cs index 528be52..2005b1a 100644 --- a/Exception.cs +++ b/Exception.cs @@ -9,6 +9,13 @@ public class HBException : Exception { : base(message, inner) {} } +public class ObjectNotFoundException : HBException { + public Guid Guid { get; set; } + + public ObjectNotFoundException(Guid guid) + : base($"Object not found: {guid}") {} +} + public class MediaException : HBException { public Media? Media { get; set; } @@ -20,6 +27,9 @@ public class MediaException : HBException { public class MediaCreateException : MediaException { public MediaCreateException(string message) : base(message) {} - public MediaCreateException(string message, Media media) - : base(message, media) {} +} + +public class ThumbnailException : MediaException { + public ThumbnailException(string message, Media media) + :base(message, media) {} } diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor index 6b8e330..b94c694 100644 --- a/Pages/Gallery.razor +++ b/Pages/Gallery.razor @@ -11,8 +11,11 @@ <link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/> @foreach(var media in Media) { + // Precalculate thumbnail size to help the browser + // lay out the images during initial page load + int width = media.Width * 200 / media.Height; <a href="/ViewMedia?m=@(media.Guid)"> - <img src="/media/thumb/@(media.Guid)?h=200" /> + <img src="/media/thumb/@(media.Guid)?h=200" width=@width height="200"/> </a> } diff --git a/Services/MediaService.cs b/Services/MediaService.cs index f26d005..0b506b0 100644 --- a/Services/MediaService.cs +++ b/Services/MediaService.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using MimeDetective; using MimeDetective.Definitions; using System.Security.Cryptography; +using System.Text.RegularExpressions; namespace HyperBooru.Services; @@ -24,6 +25,10 @@ public interface IMediaService { public void Delete(Guid media); public void Delete(Media media); + public void DeleteThumbnails(Guid media); + public void DeleteThumbnails(Media media); + public Stream GetThumbnail(Guid media, int? width, int? height); + public Stream GetThumbnail(Media media, int? width, int? height); public string GetPath(Media media); public string GetPath(Media media, int width, int height); @@ -181,29 +186,22 @@ public class MediaService : IMediaService { using var db = dbFactory.CreateDbContext(); var m = db.Media.First(m => m.Guid == media); - try { - System.IO.File.Delete( - Path.Join( - config.MediaBasePath, - m.Guid.ToString().Substring(0, 2), - m.Guid.ToString().Substring(2, 2), - m.Guid.ToString())); - } catch(IOException) {} + var path = Path.Join( + config.MediaBasePath, + m.Guid.ToString().Substring(0, 2), + m.Guid.ToString().Substring(2, 2), + m.Guid.ToString()); try { - System.IO.Directory.Delete( - Path.Join( - config.MediaBasePath, - m.Guid.ToString().Substring(0, 2), - m.Guid.ToString().Substring(2, 2))); + var fileInfo = new FileInfo(path); + fileInfo.Delete(); + fileInfo.Directory?.Delete(); + fileInfo.Directory?.Parent?.Delete(); } catch(IOException) {} try { - System.IO.Directory.Delete( - Path.Join( - config.MediaBasePath, - m.Guid.ToString().Substring(0, 2))); - } catch(IOException) {} + DeleteThumbnails(media); + } catch {} db.Media.Remove(m); db.SaveChanges(); @@ -212,6 +210,77 @@ public class MediaService : IMediaService { public void Delete(Media media) => Delete(media.Guid); + public void DeleteThumbnails(Guid media) { + var dir = new DirectoryInfo(Path.Join( + config.ThumbnailBasePath, + media.ToString().Substring(0, 2), + media.ToString().Substring(2, 2))); + + var pattern = new Regex($"^{media}-[0-9]+-[0-9]+$"); + var toDelete = dir.GetFiles() + .Where(f => pattern.IsMatch(f.Name)) + .ToList(); + + List<Exception> exceptions = new(); + + foreach(var file in toDelete) { + try { + file.Delete(); + } catch(Exception e) { + exceptions.Add(e); + } + } + + try { + dir.Delete(); + dir.Parent?.Delete(); + } catch(Exception e) { + exceptions.Add(e); + } + + // TODO: wrap the AggregateException in a ThumbnailException + if(exceptions.Count() > 1) + throw new AggregateException(exceptions); + } + + public void DeleteThumbnails(Media media) => + DeleteThumbnails(media.Guid); + + public Stream GetThumbnail(Guid media, int? width, int? height) { + using var db = dbFactory.CreateDbContext(); + var m = db.Media.First(m => m.Guid == media); + if(m is null) + throw new ObjectNotFoundException(media); + + if(m.MimeType.Split("/")[0] != "image") + throw new ThumbnailException("Media object not an image", m); + + using var image = new MagickImage(GetPath(m)); + + if(width is null && height is null) + throw new ThumbnailException("Both width and height cannot be null!", m); + + if(width > image.Width || height > image.Height) + throw new ThumbnailException("Requested thumbnail size is larger than original media", m); + + #pragma warning disable CS8629 + int w = (int) (width is not null ? width : image.Width * height / image.Height); + int h = (int) (height is not null ? height : image.Height * width / image.Width); + #pragma warning restore CS8629 + + var thumbPath = GetPath(m, w, h); + + if(!System.IO.File.Exists(thumbPath)) { + image.Resize(w, h); + image.Write(thumbPath); + } + + return System.IO.File.OpenRead(thumbPath); + } + + public Stream GetThumbnail(Media media, int? width, int? height) => + GetThumbnail(media.Guid, width, height); + public string GetPath(Media media) { var fileInfo = new FileInfo( Path.Join( diff --git a/wwwroot/styles/global.css b/wwwroot/styles/global.css index 2757d09..de0a0ac 100644 --- a/wwwroot/styles/global.css +++ b/wwwroot/styles/global.css @@ -23,6 +23,10 @@ --col-switch-bg-hl: var(--col-accent-pri); } +::selection { + background: var(--col-accent-pri); +} + body { background: var(--col-bg); color: white; |
