summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Controllers/MediaController.cs123
-rw-r--r--Exception.cs25
-rw-r--r--Pages/Gallery.razor2
-rw-r--r--Pages/ViewMedia.razor18
-rw-r--r--Services/ConfigService.cs29
-rw-r--r--Services/MediaService.cs168
6 files changed, 243 insertions, 122 deletions
diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs
index b7c8314..b476dec 100644
--- a/Controllers/MediaController.cs
+++ b/Controllers/MediaController.cs
@@ -9,23 +9,18 @@ namespace HyperBooru.Controllers;
[ApiController]
[Route("/media")]
public class MediaController : Controller {
+ private IMediaService mediaService;
private IConfigService config;
- private HBContext db;
+ private HBContext db;
- private ContentInspector inspector;
+ public MediaController(
+ IMediaService mediaService,
+ IConfigService config,
+ HBContext db) {
- public MediaController(IConfigService config, HBContext db) {
- this.config = config;
- this.db = db;
-
- ContentInspectorBuilder inspectorBuilder = new() {
- Definitions =
- MimeDetective.Definitions.Default.FileTypes.Images.All()
- .Union(MimeDetective.Definitions.Default.FileTypes.Video.All())
- .ToList()
- };
-
- inspector = inspectorBuilder.Build();
+ this.mediaService = mediaService;
+ this.config = config;
+ this.db = db;
}
[HttpGet("{mediaId}")]
@@ -34,7 +29,7 @@ public class MediaController : Controller {
if(media is null)
return NotFound();
- var fs = System.IO.File.OpenRead(config.GetPath(media));
+ var fs = System.IO.File.OpenRead(mediaService.GetPath(media));
return new FileStreamResult(fs, media.MimeType);
}
@@ -52,7 +47,7 @@ public class MediaController : Controller {
if(media.MimeType.Split("/")[0] != "image")
return BadRequest("Media object not an image");
- using var image = new MagickImage(config.GetPath(media));
+ using var image = new MagickImage(mediaService.GetPath(media));
if(w is null && h is null)
return BadRequest("Both width and height cannot be null!");
@@ -63,7 +58,7 @@ public class MediaController : Controller {
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);
- var thumbPath = config.GetPath(media, width, height);
+ var thumbPath = mediaService.GetPath(media, width, height);
if(!System.IO.File.Exists(thumbPath)) {
image.Resize(new MagickGeometry(width, height));
@@ -76,16 +71,8 @@ public class MediaController : Controller {
}
[HttpDelete("{mediaId}")]
- public IActionResult Delete([FromRoute] Guid mediaId) {
- var media = db.Media.First(m => m.Guid == mediaId);
- if(media is null)
- return NotFound();
-
- System.IO.File.Delete(config.GetPath(media));
-
- db.Media.Remove(media);
- db.SaveChanges();
- return Ok();
+ public void Delete([FromRoute] Guid mediaId) {
+ mediaService.Delete(mediaId);
}
[HttpPost]
@@ -95,72 +82,28 @@ public class MediaController : Controller {
[FromForm] DateTime? lastWriteTime,
[FromForm] DateTime? createTime) {
- using var transaction = db.Database.BeginTransaction();
-
- var formFile = Request.Form.Files[0];
- if(formFile.Length < 1)
- return BadRequest("Empty file");
-
- var formStream = formFile.OpenReadStream();
-
- // Calculate the checksum using the in-memory file contents
- var hash = BitConverter
- .ToString(MD5.Create().ComputeHash(formStream))
- .Replace("-", "")
- .ToLower();
+ if(Request.Form.Files.Count == 0)
+ return BadRequest("No files");
- if(checksum is not null && hash != checksum.ToLower())
- return BadRequest("Checksum does not match");
+ Media media = new();
- var fileRecord = new UploadedFile() {
- Filename = formFile.FileName,
- OriginalChecksum = hash,
- UploadTime = DateTime.UtcNow,
- LastAccessTime = lastAccessTime,
- LastWriteTime = lastWriteTime,
- CreateTime = createTime
- };
-
- formStream.Seek(0, SeekOrigin.Begin);
- var defs = inspector.Inspect(formStream);
-
- var mime = defs.ByMimeType().FirstOrDefault()?.MimeType;
- if(mime is null)
- return BadRequest("Unsupported file type");
-
- var media = db.Media
- .FirstOrDefault(m => m.Checksum == hash);
-
- if(media is null) {
- var ingestTagDef = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
-
- media = new() {
- Checksum = hash,
- MimeType = mime,
- UploadedFiles = new() {
- fileRecord
- },
- Tags = new() {
- new() { TagDefinition = ingestTagDef }
- }
- };
-
- using var newFile = System.IO.File.Create(config.GetPath(media));
-
- formStream.Seek(0, SeekOrigin.Begin);
- formStream.CopyTo(newFile);
- newFile.Flush();
-
- db.Media.Add(media);
- } else {
- media.UploadedFiles.Add(fileRecord);
- db.Update(media);
+ foreach(var formFile in Request.Form.Files) {
+ try {
+ media = mediaService.Create(
+ formFile.OpenReadStream(),
+ formFile.FileName,
+ checksum,
+ lastAccessTime,
+ lastWriteTime,
+ createTime);
+ } catch(MediaCreateException e) {
+ return BadRequest(e.Message);
+ }
}
- db.SaveChanges();
- transaction.Commit();
-
- return Redirect($"/ViewMedia?m={media.Guid}");
+ if(Request.Form.Files.Count == 1)
+ return Redirect($"/ViewMedia?m={media.Guid}");
+ else
+ return Redirect($"/Gallery");
}
} \ No newline at end of file
diff --git a/Exception.cs b/Exception.cs
new file mode 100644
index 0000000..528be52
--- /dev/null
+++ b/Exception.cs
@@ -0,0 +1,25 @@
+namespace HyperBooru;
+
+public class HBException : Exception {
+ public HBException()
+ : base() {}
+ public HBException(string message)
+ : base(message) {}
+ public HBException(string message, Exception inner)
+ : base(message, inner) {}
+}
+
+public class MediaException : HBException {
+ public Media? Media { get; set; }
+
+ public MediaException(string message) : base(message) {}
+ public MediaException(string message, Media media) : base(message) =>
+ Media = media;
+}
+
+public class MediaCreateException : MediaException {
+ public MediaCreateException(string message)
+ : base(message) {}
+ public MediaCreateException(string message, Media media)
+ : base(message, media) {}
+}
diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor
index 62d1a27..cbf7d3e 100644
--- a/Pages/Gallery.razor
+++ b/Pages/Gallery.razor
@@ -10,7 +10,7 @@
<link rel="stylesheet" href="@(nameof(HyperBooru)).styles.css"/>
<form id="upload" action="/media" method="post" enctype="multipart/form-data">
- <input type="file" id="myFile" name="filename"/>
+ <input type="file" id="myFile" name="filename" multiple/>
<input type="submit" />
</form>
diff --git a/Pages/ViewMedia.razor b/Pages/ViewMedia.razor
index 52300e3..7d924ef 100644
--- a/Pages/ViewMedia.razor
+++ b/Pages/ViewMedia.razor
@@ -1,4 +1,5 @@
@page "/ViewMedia"
+@inject IJSRuntime jsRuntime
@inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
@inject IMediaService mediaService
@@ -75,8 +76,8 @@
<Dialog Title="Delete this media?" @ref=deleteDialog>
<div class="button-container">
- <button class="secondary" @onclick=@(() => deleteDialog.Hide())>Cancel</button>
- <button onclick="deleteMedia()" class="warning">Confirm</button>
+ <button @onclick=@(() => deleteDialog.Hide()) class="secondary">Cancel</button>
+ <button @onclick=DeleteMedia class="warning">Confirm</button>
</div>
</Dialog>
@@ -125,11 +126,15 @@
mediaTagTable.Refresh();
}
- private void SetIngest(bool ingest) {
+ private async void SetIngest(bool ingest) {
mediaService.SetIngest(media, ingest);
db.Entry(media).State = EntityState.Detached;
LoadMedia();
- StateHasChanged();
+
+ if(ingest)
+ StateHasChanged();
+ else
+ await jsRuntime.InvokeVoidAsync("history.back");
}
private bool InfoEditMode {
@@ -154,5 +159,10 @@
infoEditMode = false;
}
+ private async void DeleteMedia() {
+ mediaService.Delete(media);
+ await jsRuntime.InvokeVoidAsync("history.back");
+ }
+
public void Dispose() => db.Dispose();
}
diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs
index 814a47b..b42b80c 100644
--- a/Services/ConfigService.cs
+++ b/Services/ConfigService.cs
@@ -4,9 +4,7 @@ public interface IConfigService {
public string DataPath { get; }
public string DbConnectionString { get; }
public string MediaBasePath { get; }
-
- public string GetPath(Media media);
- public string GetPath(Media media, int width, int height);
+ public string ThumbnailBasePath { get; }
}
public class ConfigService : IConfigService {
@@ -39,7 +37,8 @@ public class ConfigService : IConfigService {
}
public string DbConnectionString =>
- config.GetConnectionString("DefaultConnection");
+ config.GetConnectionString("DefaultConnection") ??
+ throw new HBException("Unable to get default connection string");
public string MediaBasePath =>
Path.Join(DataPath, "media");
@@ -52,28 +51,6 @@ public class ConfigService : IConfigService {
InitDirectoryStructure();
}
- public string GetPath(Media media) {
- var fileInfo = new FileInfo(Path.Join(
- MediaBasePath,
- media.Guid.ToString().Substring(0, 2),
- media.Guid.ToString().Substring(2, 2),
- media.Guid.ToString()));
-
- Directory.CreateDirectory(fileInfo.Directory.FullName);
- return fileInfo.FullName;
- }
-
- public string GetPath(Media media, int width, int height) {
- var fileInfo = new FileInfo(Path.Join(
- ThumbnailBasePath,
- media.Guid.ToString().Substring(0, 2),
- media.Guid.ToString().Substring(2, 2),
- $"{media.Guid.ToString()}-{width}-{height}"));
-
- Directory.CreateDirectory(fileInfo.Directory.FullName);
- return fileInfo.FullName;
- }
-
private void InitDirectoryStructure() {
Directory.CreateDirectory(DataPath);
Directory.CreateDirectory(MediaBasePath);
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
index 1fc74cd..0ca2c1b 100644
--- a/Services/MediaService.cs
+++ b/Services/MediaService.cs
@@ -1,4 +1,7 @@
using Microsoft.EntityFrameworkCore;
+using MimeDetective;
+using MimeDetective.Definitions;
+using System.Security.Cryptography;
namespace HyperBooru.Services;
@@ -9,13 +12,43 @@ public interface IMediaService {
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);
+
+ public void Delete(Guid media);
+ public void Delete(Media media);
+ public string GetPath(Media media);
+ public string GetPath(Media media, int width, int height);
+
}
public class MediaService : IMediaService {
private IDbContextFactory<HBContext> dbFactory;
+ private IConfigService config;
+
+ private ContentInspector inspector;
+
+ public MediaService(IDbContextFactory<HBContext> dbFactory,
+ IConfigService config) {
- public MediaService(IDbContextFactory<HBContext> dbFactory) =>
this.dbFactory = dbFactory;
+ this.config = config;
+
+ ContentInspectorBuilder inspectorBuilder = new() {
+ Definitions =
+ Default.FileTypes.Images.All()
+ .Union(Default.FileTypes.Video.All())
+ .ToList()
+ };
+
+ inspector = inspectorBuilder.Build();
+ }
public void SetIngest(Media media, bool ingest) {
using var db = dbFactory.CreateDbContext();
@@ -54,4 +87,137 @@ public class MediaService : IMediaService {
db.SaveChanges();
}
+
+ public Media Create(
+ Stream fileData,
+ string fileName,
+ string? checksum = null,
+ DateTime? lastAccessTime = null,
+ DateTime? lastWriteTime = null,
+ DateTime? createTime = 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");
+
+ var fileRecord = new UploadedFile() {
+ Filename = fileName,
+ OriginalChecksum = hash,
+ UploadTime = DateTime.UtcNow,
+ LastAccessTime = lastAccessTime,
+ LastWriteTime = lastWriteTime,
+ CreateTime = createTime
+ };
+
+ 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");
+
+ var media = db.Media
+ .FirstOrDefault(m => m.Checksum == hash);
+
+ if(media is null) {
+ var ingestTagDef = db.TagDefinitions
+ .First(td => td.Guid == HBContext.IngestTag);
+
+ media = new() {
+ Checksum = hash,
+ MimeType = mime,
+ UploadedFiles = new() {
+ fileRecord
+ },
+ Tags = new() {
+ new() { TagDefinition = ingestTagDef }
+ }
+ };
+
+ using var newFile = System.IO.File.Create(GetPath(media));
+
+ fileData.Seek(0, SeekOrigin.Begin);
+ fileData.CopyTo(newFile);
+ newFile.Flush();
+
+ db.Media.Add(media);
+ } else {
+ media.UploadedFiles.Add(fileRecord);
+ 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);
+
+ 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) {}
+
+ try {
+ System.IO.Directory.Delete(
+ Path.Join(
+ config.MediaBasePath,
+ m.Guid.ToString().Substring(0, 2),
+ m.Guid.ToString().Substring(2, 2)));
+ } catch(IOException) {}
+
+ try {
+ System.IO.Directory.Delete(
+ Path.Join(
+ config.MediaBasePath,
+ m.Guid.ToString().Substring(0, 2)));
+ } catch(IOException) {}
+
+ db.Media.Remove(m);
+ db.SaveChanges();
+ }
+
+ public void Delete(Media media) =>
+ Delete(media.Guid);
+
+ public string GetPath(Media media) {
+ var fileInfo = new FileInfo(
+ Path.Join(
+ config.MediaBasePath,
+ media.Guid.ToString().Substring(0, 2),
+ media.Guid.ToString().Substring(2, 2),
+ media.Guid.ToString()));
+
+ Directory.CreateDirectory(fileInfo.Directory.FullName);
+ return fileInfo.FullName;
+ }
+
+ public string GetPath(Media media, int width, int height) {
+ var fileInfo = new FileInfo(Path.Join(
+ config.ThumbnailBasePath,
+ media.Guid.ToString().Substring(0, 2),
+ media.Guid.ToString().Substring(2, 2),
+ $"{media.Guid.ToString()}-{width}-{height}"));
+
+ Directory.CreateDirectory(fileInfo.Directory.FullName);
+ return fileInfo.FullName;
+ }
}