summaryrefslogtreecommitdiff
path: root/Services/MediaService.cs
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2026-05-22 00:52:16 +1000
committerJake Mannens <jake@asger.xyz>2026-05-23 22:22:55 +1000
commit12eaa5814ef20b0910e8d64a753378b6f6797989 (patch)
tree062cf477c29054e0f089cb80f0cd79a9f3b7ccd9 /Services/MediaService.cs
parent6de5d7f5364fe1d54703da6d6b7cb08ea26e939f (diff)
Initial commitwasm-initial
Diffstat (limited to 'Services/MediaService.cs')
-rw-r--r--Services/MediaService.cs400
1 files changed, 0 insertions, 400 deletions
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
deleted file mode 100644
index e497570..0000000
--- a/Services/MediaService.cs
+++ /dev/null
@@ -1,400 +0,0 @@
-using HyperBooru.ApiModels;
-using ImageMagick;
-using Microsoft.EntityFrameworkCore;
-using MimeDetective;
-using MimeDetective.Definitions;
-using System.Security.Cryptography;
-using System.Text.RegularExpressions;
-
-namespace HyperBooru.Services;
-
-public interface IMediaService {
- public void SetDescription(
- Media media,
- string? shortDescription,
- string? longDescription);
-
- public void SetIngest(Media media, bool ingest);
-
- public Media Create(
- Stream fileData,
- string fileName,
- string? checksum = null,
- DateTime? lastAccessTime = null,
- DateTime? lastWriteTime = null,
- DateTime? createTime = null,
- string? path = null,
- PathType? pathType = null,
- Guid[]? tagIds = null);
-
- 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 Stream GetConverted(Guid mediaId, string mimeType = "image/png");
- public Stream GetConverted(Media media, string mimeType = "image/png");
- public string GetPath(Guid media);
- public string GetPath(Media media);
-
-}
-
-public class MediaService : IMediaService {
- private readonly Dictionary<string,MagickFormat> FormatMap = new() {
- ["image/jpeg"] = MagickFormat.Jpeg,
- ["image/jpg"] = MagickFormat.Jpg,
- ["image/png"] = MagickFormat.Png,
- ["image/webp"] = MagickFormat.WebP
- };
-
- private IDbContextFactory<HBContext> dbFactory;
- private IConfigService config;
-
- private IContentInspector inspector;
-
- public MediaService(IDbContextFactory<HBContext> dbFactory,
- IConfigService config) {
-
- this.dbFactory = dbFactory;
- this.config = config;
-
- ContentInspectorBuilder inspectorBuilder = new() {
- Definitions =
- DefaultDefinitions.FileTypes.Images.All()
- .Union(DefaultDefinitions.FileTypes.Video.All())
- .ToList()
- };
-
- inspector = inspectorBuilder.Build();
- }
-
- public void SetIngest(Media media, bool ingest) {
- using var db = dbFactory.CreateDbContext();
- media = db.Media
- .Include(m => m.Tags)
- .ThenInclude(t => t.TagDefinition)
- .First(m => m.Guid == media.Guid);
- var ingestTag = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
-
- if(ingest) {
- if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(HBContext.IngestTag))
- media.Tags.Add(new(ingestTag));
- } else {
- media.Tags.RemoveAll(t => t.TagDefinition.Guid == HBContext.IngestTag);
- }
-
- db.SaveChanges();
- }
-
- public void SetDescription(
- Media media,
- string? shortDescription,
- string? longDescription) {
-
- using var db = dbFactory.CreateDbContext();
- var m = db.Media.First(m => m.Guid == media.Guid);
-
- shortDescription = shortDescription?.Trim();
- longDescription = longDescription?.Trim();
-
- if(string.IsNullOrEmpty(shortDescription))
- shortDescription = null;
- if(string.IsNullOrEmpty(longDescription))
- longDescription = null;
-
- m.ShortDescription = shortDescription;
- m.LongDescription = longDescription;
-
- db.SaveChanges();
- }
-
- public Media Create(
- Stream fileData,
- string fileName,
- string? checksum = null,
- DateTime? lastAccessTime = null,
- DateTime? lastWriteTime = null,
- DateTime? createTime = null,
- string? path = null,
- PathType? pathType = null,
- Guid[]? tagIds = null) {
-
- using var db = dbFactory.CreateDbContext();
- using var transaction = db.Database.BeginTransaction();
-
- if(fileData.Length == 0)
- throw new MediaCreateException("File is empty");
-
- // Calculate the checksum using the in-memory file contents
- var hash = BitConverter
- .ToString(MD5.Create().ComputeHash(fileData))
- .Replace("-", "")
- .ToLower();
-
- if(checksum is not null && hash != checksum.ToLower())
- throw new MediaCreateException("Checksum does not match");
-
- // Determine the MIME type
- fileData.Seek(0, SeekOrigin.Begin);
- var defs = inspector.Inspect(fileData);
- var mime = defs.ByMimeType().FirstOrDefault()?.MimeType;
- if(mime is null)
- throw new MediaCreateException("Unsupported file type");
-
- // Read the image with ImageMagick to determine the width and height
- fileData.Seek(0, SeekOrigin.Begin);
- using var magickImage = new MagickImage(fileData);
-
- var media = db.Media
- .Include(m => m.UploadedFiles)
- .Include(m => m.Tags)
- .FirstOrDefault(m => m.UploadedFiles.Any(uf => uf.Checksum == hash));
-
- var fileRecord = new UploadedFile() {
- Filename = fileName,
- Length = fileData.Length,
- Checksum = hash,
- ChecksumVerified = checksum is not null,
- MimeType = mime,
- Width = (int) magickImage.Width,
- Height = (int) magickImage.Height,
- UploadTime = DateTime.UtcNow,
- LastAccessTime = lastAccessTime,
- LastWriteTime = lastWriteTime,
- CreateTime = createTime,
- Path = pathType is null ? null : path,
- PathType = pathType
- };
-
- var tags = Array.Empty<TagDefinition>();
- if(tagIds is not null) {
- tagIds = tagIds.Distinct().ToArray();
-
- tags = db.TagDefinitions
- .Where(td => tagIds.Contains(td.Guid))
- .ToArray();
-
- if(tags.Count() < tagIds.Count()) {
- var badIds = tagIds
- .Where(x => !tags.Select(td => td.Guid).Contains(x))
- .Order();
-
- throw new MediaCreateException(
- $"Non-existent tags specified: {string.Join(", ", badIds)}");
- }
- }
-
- if(media is null) {
- var ingestTagDef = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
-
- media = new() {
- UploadedFiles = new() {
- fileRecord
- },
- Tags = tags is null ? [ new() { TagDefinition = ingestTagDef } ] : tags
- .Select(td => new Tag() { TagDefinition = td })
- .ToList()
- };
-
- using var newFile = File.Create(GetPath(media));
-
- fileData.Seek(0, SeekOrigin.Begin);
- fileData.CopyTo(newFile);
- newFile.Flush();
-
- db.Media.Add(media);
- db.SaveChanges();
- media.CurrentUploadedFile = fileRecord;
- db.SaveChanges();
- } else {
- var fileHashes = media.UploadedFiles
- .Select(uf => GetUploadedFileHash(uf));
- // Only add the uploaded file record if it contains new information
- if(!fileHashes.Contains(GetUploadedFileHash(fileRecord)))
- media.UploadedFiles.Add(fileRecord);
- // Add new tags if needed
- var missingTags = tags
- .Where(td => !media.Tags.Select(t => t.TagDefinition.Guid).Contains(td.Guid));
- media.Tags.AddRange(missingTags.Select(td => new Tag() { TagDefinition = td }));
- db.Update(media);
- db.SaveChanges();
- }
-
- transaction.Commit();
-
- return media;
- }
-
- public void Delete(Guid media) {
- using var db = dbFactory.CreateDbContext();
- var m = db.Media.First(m => m.Guid == media);
-
- var path = Path.Join(
- config.MediaBasePath,
- m.Guid.ToString().Substring(0, 2),
- m.Guid.ToString().Substring(2, 2),
- m.Guid.ToString());
-
- try {
- var fileInfo = new FileInfo(path);
- fileInfo.Delete();
- fileInfo.Directory?.Delete();
- fileInfo.Directory?.Parent?.Delete();
- } catch(IOException) {}
-
- try {
- DeleteThumbnails(media);
- } catch {}
-
- db.Media.Remove(m);
- db.SaveChanges();
- }
-
- 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 mediaId, int? width, int? height) {
- if(width is null && height is null)
- throw new ThumbnailException(
- "Both width and height cannot be null!",
- mediaId);
-
- var thumbPath = GetThumbnailPath(mediaId, width, height);
-
- if(File.Exists(thumbPath))
- return System.IO.File.OpenRead(thumbPath);
-
- if(!File.Exists(GetPath(mediaId)))
- throw new ObjectNotFoundException(mediaId);
-
- using var image = new MagickImage(GetPath(mediaId));
-
- if(width > image.Width || height > image.Height) {
- width = (int) image.Width;
- height = (int) image.Height;
- }
-
- image.Thumbnail((uint) (width ?? -1), (uint) (height ?? -1));
- image.Write(thumbPath, MagickFormat.Jpeg);
-
- return System.IO.File.OpenRead(thumbPath);
- }
-
- public Stream GetConverted(Guid mediaId, string mimeType) {
- if(!FormatMap.TryGetValue(mimeType, out var format))
- throw new MediaException($"Cannot convert to unknown format ({mimeType})", mediaId);
-
- var convertedPath = GetConvertedPath(mediaId, mimeType);
-
- if(File.Exists(convertedPath))
- return System.IO.File.OpenRead(convertedPath);
-
- if(!File.Exists(GetPath(mediaId)))
- throw new ObjectNotFoundException(mediaId);
-
- using var image = new MagickImage(GetPath(mediaId));
- image.Write(convertedPath, format);
-
- return System.IO.File.OpenRead(convertedPath);
- }
-
- public Stream GetThumbnail(Media media, int? width, int? height) =>
- GetThumbnail(media.Guid, width, height);
-
- public Stream GetConverted(Media media, string mimeType) =>
- GetConverted(media.Guid, mimeType);
-
- public string GetPath(Guid mediaId) {
- var fileInfo = new FileInfo(
- Path.Join(
- config.MediaBasePath,
- mediaId.ToString().Substring(0, 2),
- mediaId.ToString().Substring(2, 2),
- mediaId.ToString()));
-
- Directory.CreateDirectory(fileInfo.Directory!.FullName);
- return fileInfo.FullName;
- }
-
- public string GetThumbnailPath(Guid mediaId, int? width, int? height) {
- if(width is null && height is null)
- throw new ThumbnailException(
- "Both width and height cannot be null!",
- mediaId);
-
- var fileInfo = new FileInfo(Path.Join(
- config.ThumbnailBasePath,
- mediaId.ToString().Substring(0, 2),
- mediaId.ToString().Substring(2, 2),
- $"{mediaId.ToString()}-{(width ?? 0)}-{(height ?? 0)}"));
-
- Directory.CreateDirectory(fileInfo.Directory!.FullName);
- return fileInfo.FullName;
- }
-
- public string GetConvertedPath(Guid mediaId, string mimeType) {
- var fileInfo = new FileInfo(Path.Join(
- config.ConvertedMediaBasePath,
- mediaId.ToString().Substring(0, 2),
- mediaId.ToString().Substring(2, 2),
- $"{mediaId.ToString()}-{mimeType.Split('/')[1]}"));
-
- Directory.CreateDirectory(fileInfo.Directory!.FullName);
- return fileInfo.FullName;
- }
-
- public string GetPath(Media media) =>
- GetPath(media.Guid);
-
- public string GetThumbnailPath(Media media, int? width, int? height) =>
- GetThumbnailPath(media.Guid, width, height);
-
- public string GetConvertedPath(Media media, string mimeType) =>
- GetConvertedPath(media.Guid, mimeType);
-
- private int GetUploadedFileHash(UploadedFile uf) => (
- uf.CreateTime,
- uf.LastWriteTime,
- uf.Filename,
- uf.Length,
- uf.Checksum).GetHashCode();
-}