diff options
Diffstat (limited to 'Services/MediaService.cs')
| -rw-r--r-- | Services/MediaService.cs | 400 |
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(); -} |
