summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2026-03-25 01:57:19 +1100
committerJake Mannens <jake@asger.xyz>2026-03-25 01:57:46 +1100
commit6c06dfc4f83f30292e65c08a3cb0c48401d4bfa7 (patch)
tree511f88873fa6173637115a38c31ec5f8018e108e
parentc751709b1b4fe6f16fd84647e8e071455e7b78d6 (diff)
v0.2av0.2a
-rw-r--r--Controllers/MediaController.cs2
-rw-r--r--Exception.cs10
-rw-r--r--Pages/Component/MediaTagTable.razor2
-rw-r--r--Pages/Gallery.razor131
-rw-r--r--Pages/Gallery.razor.css2
-rw-r--r--Pages/TagDefinitions.razor2
-rw-r--r--Program.cs2
-rw-r--r--Server.csproj6
-rw-r--r--Services/ConfigService.cs4
-rw-r--r--Services/FeedService.cs155
-rw-r--r--Services/MediaService.cs75
-rw-r--r--Services/OcrService.cs18
-rw-r--r--Services/SearchService.cs117
13 files changed, 280 insertions, 246 deletions
diff --git a/Controllers/MediaController.cs b/Controllers/MediaController.cs
index 3368f45..2c015f7 100644
--- a/Controllers/MediaController.cs
+++ b/Controllers/MediaController.cs
@@ -32,7 +32,7 @@ public class MediaController : Controller {
var fs = System.IO.File.OpenRead(mediaService.GetPath(media));
- return new FileStreamResult(fs, media.CurrentUploadedFile.MimeType);
+ return new FileStreamResult(fs, media.CurrentUploadedFile!.MimeType);
}
[HttpGet("thumb/{mediaId}")]
diff --git a/Exception.cs b/Exception.cs
index 1e070eb..fc3feda 100644
--- a/Exception.cs
+++ b/Exception.cs
@@ -47,11 +47,13 @@ public class TagDuplicateException : TagException {
}
public class MediaException : HBException {
- public Media? Media { get; private init; }
+ public Guid? MediaId { get; private init; } = null;
public MediaException(string message) : base(message) {}
+ public MediaException(string message, Guid mediaId) : base(message) =>
+ MediaId = mediaId;
public MediaException(string message, Media media) : base(message) =>
- Media = media;
+ MediaId = media.Guid;
}
public class MediaCreateException : MediaException {
@@ -60,6 +62,8 @@ public class MediaCreateException : MediaException {
}
public class ThumbnailException : MediaException {
+ public ThumbnailException(string message, Guid mediaId)
+ : base(message, mediaId) {}
public ThumbnailException(string message, Media media)
- :base(message, media) {}
+ : base(message, media) {}
}
diff --git a/Pages/Component/MediaTagTable.razor b/Pages/Component/MediaTagTable.razor
index e367f7f..e687529 100644
--- a/Pages/Component/MediaTagTable.razor
+++ b/Pages/Component/MediaTagTable.razor
@@ -19,7 +19,7 @@
}
</td>
<td>
- <a href="/Gallery?q=@(e.tagDef.Name)" class="nondecorated">
+ <a href="/Gallery?t=@(e.tagDef.Guid)" class="nondecorated">
@if(e.isImplicit) {
<i>@e.tagDef.Name</i>
} else {
diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor
index 762ef7f..c037979 100644
--- a/Pages/Gallery.razor
+++ b/Pages/Gallery.razor
@@ -1,8 +1,7 @@
@page "/"
@page "/Gallery"
-@inject IDbContextFactory<HBContext> dbFactory
@inject ITagService tagService
-@inject ISearchService searchService
+@inject IFeedService feedService
@inject IUserService userService
@inject IJSRuntime jsRuntime
@implements IDisposable
@@ -11,31 +10,36 @@
<PageTitle>@Title</PageTitle>
@if(Ingest && !userService.UserSessionState.ShowNsfw) {
- <div id="ingest-warning">
+ <div id="feed-error">
<p><center>Ingest feed is not available unless NSFW mode is enabled!</center></p>
<p><center><i>You must enable NSFW mode to continue...</i></center></p>
</div>
-}
-
-<div style="padding:var(--size-default-gap);">
- @foreach(var media in displayMedia) {
- // Precalculate thumbnail size to help the browser
- // lay out the images during initial page load
- int width = (int) media.CurrentUploadedFile.Width! * 200 / (int) media.CurrentUploadedFile.Height!;
- <a href="/ViewMedia?m=@(media.Guid)">
- <img src="/media/thumb/@(media.Guid)?h=200" width=@width height="200"/>
- </a>
- }
-</div>
+} else if(TagId is not null && Query is not null) {
+ <div id="feed-error">
+ <p><center>Invalid query parameters! Both a search query and</center></p>
+ <p><center>a tag ID have been specified!</center></p>
+ </div>
+} else {
+ <div style="padding:var(--size-default-gap);">
+ @foreach(var media in displayMedia) {
+ // Precalculate thumbnail size to help the browser
+ // lay out the images during initial page load
+ int width = (int) media.CurrentUploadedFile!.Width! * 200 / (int) media.CurrentUploadedFile.Height!;
+ <a href="/ViewMedia?m=@(media.Guid)">
+ <img src="/media/thumb/@(media.Guid)?h=200" width=@width height="200"/>
+ </a>
+ }
+ </div>
-<div id="canary"/>
+ <div id="canary"/>
+}
<script suppress-error="BL9992">
function registerScrollObserver(dotNetObject) {
var scrollObserver = new IntersectionObserver(
async (e) => {
if(e[0].isIntersecting) {
- await dotNetObject.invokeMethodAsync('LoadMore');
+ await dotNetObject.invokeMethodAsync('LoadMedia', false);
}
},
{ threshold: [1] });
@@ -45,6 +49,10 @@
@code {
[Parameter]
+ [SupplyParameterFromQuery(Name = "t")]
+ public Guid? TagId { get; set; }
+
+ [Parameter]
[SupplyParameterFromQuery(Name = "q")]
public string? Query { get; set; }
@@ -64,13 +72,11 @@
}
private List<Media> displayMedia;
- private Media[] queryResult;
- private IEnumerator<Media> mediaEnumerator;
protected override void OnInitialized() =>
userService.UserSessionState.OnStateChange += ShowNsfwChanged;
- protected override void OnParametersSet() => LoadMedia();
+ protected override void OnParametersSet() => LoadMedia(true);
protected override void OnAfterRender(bool firstRender) {
if(firstRender)
@@ -79,71 +85,44 @@
DotNetObjectReference.Create(this));
}
- private void LoadMedia() {
- using var db = dbFactory.CreateDbContext();
+ [JSInvokable("LoadMedia")]
+ public void LoadMedia(bool initial = false) {
+ Media? key = displayMedia?.Any() ?? false && !initial ? displayMedia.Last() : null;
+
+ if(initial)
+ displayMedia = new();
- if(Query is not null) {
- queryResult = searchService.Search(Query)
- .OrderByDescending(m => m.ObjectId)
- .ToArray();
+ if(TagId is not null && Query is null) {
+ displayMedia!.AddRange(feedService.LoadChunk(
+ selectIngest: Ingest,
+ includeNsfw: userService.UserSessionState.ShowNsfw,
+ tagId: (Guid) TagId!,
+ key: key,
+ count: PageSize));
+ } else if(Query is not null && TagId is null) {
+ displayMedia!.AddRange(feedService.LoadChunk(
+ selectIngest: Ingest,
+ includeNsfw: userService.UserSessionState.ShowNsfw,
+ query: string.IsNullOrWhiteSpace(Query) ? null : Query,
+ key: key,
+ count: PageSize));
} else {
- queryResult = db.Media
- .Include(m => m.Tags)
- .Include(m => m.CurrentUploadedFile)
- .OrderByDescending(m => m.ObjectId)
- .ToArray();
+ displayMedia!.AddRange(feedService.LoadChunk(
+ selectIngest: Ingest,
+ includeNsfw: userService.UserSessionState.ShowNsfw,
+ key: key,
+ count: PageSize));
}
- mediaEnumerator = FilterMedia(queryResult).GetEnumerator();
-
- displayMedia = new();
-
- LoadMore();
- }
-
- [JSInvokable("LoadMore")]
- public void LoadMore() {
- for(int i = 0; i < PageSize; i++) {
- if(!mediaEnumerator.MoveNext())
- break;
- displayMedia.Add(mediaEnumerator.Current);
- }
- StateHasChanged();
- }
-
- private IEnumerable<Media> FilterMedia(IEnumerable<Media> media) {
- var nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
- .Select(td => td.ObjectId)
- .ToArray();
-
- using var enumerator = media.GetEnumerator();
-
- while(true) {
- bool success = enumerator.MoveNext();
- if(!success)
- break;
- Media? m = enumerator.Current;
-
- if(!userService.UserSessionState.ShowNsfw)
- if(m.Tags.Select(t => t.TagDefinitionId).Intersect(nsfwTags).Any() || m.IsIngest)
- continue;
-
- if(m.IsIngest != Ingest)
- continue;
-
- yield return m;
- }
+ StateHasChanged();
}
private async void ShowNsfwChanged(UserSessionState userSessionState) {
await InvokeAsync(() => {
- LoadMedia();
- StateHasChanged();
+ LoadMedia(true);
});
}
- public void Dispose() {
- mediaEnumerator.Dispose();
- userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
- }
+ public void Dispose() =>
+ userService.UserSessionState.OnStateChange -= ShowNsfwChanged;
}
diff --git a/Pages/Gallery.razor.css b/Pages/Gallery.razor.css
index 0e01e0e..989e252 100644
--- a/Pages/Gallery.razor.css
+++ b/Pages/Gallery.razor.css
@@ -3,7 +3,7 @@
max-height: 200px;
}
-div#ingest-warning {
+div#feed-error {
position: relative;
top: 50%;
left: 50%;
diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor
index e2e4df6..f3dca0f 100644
--- a/Pages/TagDefinitions.razor
+++ b/Pages/TagDefinitions.razor
@@ -26,7 +26,7 @@
<tr data-guid="@tagDef.Guid">
<td>@tagDef.Alias</td>
<td>
- <a href="/Gallery?q=@tagDef.Name" class="nondecorated">
+ <a href="/Gallery?t=@tagDef.Guid" class="nondecorated">
@tagDef.Name
</a>
</td>
diff --git a/Program.cs b/Program.cs
index f80b996..e7f1e26 100644
--- a/Program.cs
+++ b/Program.cs
@@ -22,7 +22,7 @@ public class Program {
// Add our custom services
builder.Services.AddSingleton<IConfigService, ConfigService>();
builder.Services.AddDbContextFactory<HBContext>();
- builder.Services.AddScoped<ISearchService, SearchService>();
+ builder.Services.AddScoped<IFeedService, FeedService>();
builder.Services.AddScoped<ITagService, TagService>();
builder.Services.AddScoped<IMediaService, MediaService>();
builder.Services.AddSingleton<IGlobalUserService, GlobalUserService>();
diff --git a/Server.csproj b/Server.csproj
index e3ca595..7ce894a 100644
--- a/Server.csproj
+++ b/Server.csproj
@@ -6,9 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<AssemblyName>HyperBooru</AssemblyName>
<RootNamespace>HyperBooru</RootNamespace>
- <AssemblyVersion>0.1.0.0</AssemblyVersion>
+ <AssemblyVersion>0.2.0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
- <Version>0.1-alpha</Version>
+ <Version>0.2-alpha</Version>
<UserSecretsId>2907567f-4640-4581-8f4d-0977952d26bd</UserSecretsId>
</PropertyGroup>
@@ -21,7 +21,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.2" />
+ <PackageReference Include="Magick.NET-Q16-AnyCPU" Version="14.10.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.23" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.23">
<PrivateAssets>all</PrivateAssets>
diff --git a/Services/ConfigService.cs b/Services/ConfigService.cs
index b42b80c..8460fd0 100644
--- a/Services/ConfigService.cs
+++ b/Services/ConfigService.cs
@@ -5,6 +5,7 @@ public interface IConfigService {
public string DbConnectionString { get; }
public string MediaBasePath { get; }
public string ThumbnailBasePath { get; }
+ public bool EnableOcr { get; }
}
public class ConfigService : IConfigService {
@@ -46,6 +47,9 @@ public class ConfigService : IConfigService {
public string ThumbnailBasePath =>
Path.Join(DataPath, "thumb");
+ public bool EnableOcr =>
+ bool.TryParse(config["DisableOcr"], out bool x) ? !x : true;
+
public ConfigService(IConfiguration config) {
this.config = config;
InitDirectoryStructure();
diff --git a/Services/FeedService.cs b/Services/FeedService.cs
new file mode 100644
index 0000000..864a751
--- /dev/null
+++ b/Services/FeedService.cs
@@ -0,0 +1,155 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace HyperBooru.Services;
+
+public interface IFeedService {
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Media? key = null,
+ int count = 50);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ string query,
+ Media? key = null,
+ int count = 50);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Guid tagId,
+ Media? key = null,
+ int count = 50);
+}
+
+public class FeedService : IFeedService {
+ private IDbContextFactory<HBContext> dbFactory;
+
+ public FeedService(IDbContextFactory<HBContext> dbFactory) =>
+ this.dbFactory = dbFactory;
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, null, null, key, count);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ string query,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, query, null, key, count);
+
+ public Media[] LoadChunk(
+ bool selectIngest,
+ bool includeNsfw,
+ Guid tagId,
+ Media? key,
+ int count) => LoadChunkInternal(selectIngest, includeNsfw, null, tagId, key, count);
+
+ private Media[] LoadChunkInternal(
+ bool selectIngest,
+ bool includeNsfw,
+ string? query,
+ Guid? tagId,
+ Media? key,
+ int count) {
+
+ if(selectIngest && !includeNsfw)
+ return Array.Empty<Media>();
+
+ using var db = dbFactory.CreateDbContext();
+
+ IQueryable<Media> media = db.Media
+ .AsSingleQuery()
+ .AsNoTracking()
+ .Include(m => m.Tags)
+ .Include(m => m.CurrentUploadedFile);
+
+ if(!includeNsfw)
+ media = media
+ .Where(m => !TagsThatImply(db, HBContext.NsfwTag)
+ .Intersect(m.Tags.Select(t => t.TagDefinitionId))
+ .Any());
+
+ if(selectIngest) {
+ media = media
+ .Where(m => m.Tags
+ .Select(t => t.TagDefinitionId)
+ .Contains((int) HBObjectId.IngestTag));
+ } else {
+ media = media
+ .Where(m => !m.Tags
+ .Select(t => t.TagDefinitionId)
+ .Contains((int) HBObjectId.IngestTag));
+ }
+
+ if(query is not null) {
+ media = Search(media, query);
+ } else if(tagId is not null) {
+ media = media
+ .Where(m => TagsThatImply(db, (Guid) tagId)
+ .Intersect(m.Tags.Select(t => t.TagDefinitionId))
+ .Any());
+ }
+
+ if(key is not null)
+ media = media.Where(m => m.ObjectId > key.ObjectId);
+
+ return media
+ .OrderBy(m => m.ObjectId)
+ .Take(count)
+ .ToArray();
+ }
+
+ private static IQueryable<Media> Search(IQueryable<Media> media, string query) {
+ // TODO: search implicit tags as well
+
+ query = query.ToLower().Trim();
+
+ return media
+ .Where(m =>
+ (m.ShortDescription != null && m.ShortDescription.ToLower().Contains(query)) ||
+ (m.LongDescription != null && m.LongDescription.ToLower().Contains(query)) ||
+ (m.UploadedFiles.Any(uf => uf.Filename != null && uf.Filename.ToLower().Contains(query))) ||
+ (m.OcrData != null && m.OcrData.SearchableText.ToLower().Contains(query)) ||
+ (m.Tags.Any(t => t.TagDefinition.Name.ToLower().Contains(query))));
+ }
+
+ private static IQueryable<int> TagsThatImply(HBContext db, Guid tagId) =>
+ db.Database.SqlQueryRaw<int>("""
+ WITH RECURSIVE basetag AS (
+ SELECT "ObjectId" FROM "Objects" WHERE "Guid" = {0}
+ ),
+ impliedtags AS (
+ SELECT
+ "TagDefinitionObjectId"
+ FROM
+ "TagDefinitionTagDefinition"
+ INNER JOIN
+ basetag
+ ON
+ "ImplicitTagsObjectId" = basetag."ObjectId"
+ UNION
+ SELECT
+ "TagDefinitionTagDefinition"."TagDefinitionObjectId"
+ FROM
+ "TagDefinitionTagDefinition"
+ INNER JOIN
+ impliedtags
+ ON
+ impliedtags."TagDefinitionObjectId" = "TagDefinitionTagDefinition"."ImplicitTagsObjectId"
+ )
+ SELECT DISTINCT
+ "TagDefinitionObjectId" AS "Value"
+ FROM impliedtags
+ UNION
+ SELECT
+ "ObjectId" AS "Value"
+ FROM
+ basetag
+ """, tagId);
+}
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
index 104d0db..a5803f9 100644
--- a/Services/MediaService.cs
+++ b/Services/MediaService.cs
@@ -31,8 +31,10 @@ public interface IMediaService {
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(Guid media);
+ public string GetPath(Guid media, int? width, int? height);
public string GetPath(Media media);
- public string GetPath(Media media, int width, int height);
+ public string GetPath(Media media, int? width, int? height);
}
@@ -259,37 +261,29 @@ public class MediaService : IMediaService {
public void DeleteThumbnails(Media media) =>
DeleteThumbnails(media.Guid);
- public Stream GetThumbnail(Guid media, int? width, int? height) {
- using var db = dbFactory.CreateDbContext();
+ 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 m = db.Media
- .Include(m => m.CurrentUploadedFile)
- .First(m => m.Guid == media);
- if(m is null)
- throw new ObjectNotFoundException(media);
+ var thumbPath = GetPath(mediaId, width, height);
- if(m.CurrentUploadedFile.MimeType.Split("/")[0] != "image")
- throw new ThumbnailException("Media object not an image", m);
+ if(File.Exists(thumbPath))
+ return System.IO.File.OpenRead(thumbPath);
- using var image = new MagickImage(GetPath(m));
+ if(!File.Exists(GetPath(mediaId)))
+ throw new ObjectNotFoundException(mediaId);
- if(width is null && height is null)
- throw new ThumbnailException("Both width and height cannot be null!", m);
+ using var image = new MagickImage(GetPath(mediaId));
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);
+ throw new ThumbnailException(
+ "Requested thumbnail size is larger than original media",
+ mediaId);
- if(!File.Exists(thumbPath)) {
- image.Resize((uint) w, (uint) h);
- image.Write(thumbPath);
- }
+ image.Thumbnail((uint) (width ?? -1), (uint) (height ?? -1));
+ image.Write(thumbPath, MagickFormat.Jpeg);
return System.IO.File.OpenRead(thumbPath);
}
@@ -297,29 +291,40 @@ public class MediaService : IMediaService {
public Stream GetThumbnail(Media media, int? width, int? height) =>
GetThumbnail(media.Guid, width, height);
- public string GetPath(Media media) {
+ public string GetPath(Guid mediaId) {
var fileInfo = new FileInfo(
Path.Join(
config.MediaBasePath,
- media.Guid.ToString().Substring(0, 2),
- media.Guid.ToString().Substring(2, 2),
- media.Guid.ToString()));
+ mediaId.ToString().Substring(0, 2),
+ mediaId.ToString().Substring(2, 2),
+ mediaId.ToString()));
- Directory.CreateDirectory(fileInfo.Directory.FullName);
+ Directory.CreateDirectory(fileInfo.Directory!.FullName);
return fileInfo.FullName;
}
- public string GetPath(Media media, int width, int height) {
+ public string GetPath(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,
- media.Guid.ToString().Substring(0, 2),
- media.Guid.ToString().Substring(2, 2),
- $"{media.Guid.ToString()}-{width}-{height}"));
+ mediaId.ToString().Substring(0, 2),
+ mediaId.ToString().Substring(2, 2),
+ $"{mediaId.ToString()}-{(width ?? 0)}-{(height ?? 0)}"));
- Directory.CreateDirectory(fileInfo.Directory.FullName);
+ Directory.CreateDirectory(fileInfo.Directory!.FullName);
return fileInfo.FullName;
}
+ public string GetPath(Media media) =>
+ GetPath(media.Guid);
+
+ public string GetPath(Media media, int? width, int? height) =>
+ GetPath(media.Guid, width, height);
+
private int GetUploadedFileHash(UploadedFile uf) => (
uf.CreateTime,
uf.LastWriteTime,
diff --git a/Services/OcrService.cs b/Services/OcrService.cs
index 4d21705..40905aa 100644
--- a/Services/OcrService.cs
+++ b/Services/OcrService.cs
@@ -18,18 +18,21 @@ public class OcrService : IHostedService {
private Timer timer;
+ private IConfigService configService;
private IServiceScopeFactory scopeFactory;
private ILogger<OcrService> logger;
private IDbContextFactory<HBContext> dbFactory;
public OcrService(
+ IConfigService configService,
IServiceScopeFactory scopeFactory,
ILogger<OcrService> logger,
IDbContextFactory<HBContext> dbFactory) {
- this.scopeFactory = scopeFactory;
- this.logger = logger;
- this.dbFactory = dbFactory;
+ this.configService = configService;
+ this.scopeFactory = scopeFactory;
+ this.logger = logger;
+ this.dbFactory = dbFactory;
timer = new((object? state) => {
if(task is not null && !task.IsCompleted)
@@ -40,8 +43,11 @@ public class OcrService : IHostedService {
}
public Task StartAsync(CancellationToken ct) {
- logger.LogInformation("Service starting...");
- timer.Change(StartupDelay, ProcessInterval);
+ if(configService.EnableOcr) {
+ logger.LogInformation("Service starting...");
+ timer.Change(StartupDelay, ProcessInterval);
+ }
+
return Task.CompletedTask;
}
@@ -53,8 +59,6 @@ public class OcrService : IHostedService {
}
async Task ProcessAllAsync(CancellationToken ct) {
- return;
-
using var scope = scopeFactory.CreateScope();
var mediaService = scope.ServiceProvider
.GetRequiredService<IMediaService>();
diff --git a/Services/SearchService.cs b/Services/SearchService.cs
deleted file mode 100644
index 5ca12e1..0000000
--- a/Services/SearchService.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace HyperBooru.Services;
-
-public interface ISearchService {
- public Media[] Search(string query);
-}
-
-public class SearchService : ISearchService {
- private ITagService tagService;
-
- private IDbContextFactory<HBContext> dbFactory;
-
- public SearchService(
- IDbContextFactory<HBContext> dbFactory,
- ITagService tagService) {
-
- this.tagService = tagService;
- this.dbFactory = dbFactory;
- }
-
- public Media[] Search(string query) {
- var db = dbFactory.CreateDbContext();
-
- query = query.ToLower().Trim();
-
- int[] descriptionResults = SearchDescription(query);
- int[] filenameResults = SearchFilenames(query);
- int[] ocrResults = SearchOcr(query);
-
- var matchedTag = db.TagDefinitions
- .FirstOrDefault(td => td.Name.ToLower() == query);
-
- int[] tags;
- if(matchedTag is not null) {
- tags = tagService
- .TagsThatImply(matchedTag)
- .Select(td => td.ObjectId)
- .ToArray();
- } else {
- // TODO: Expand scope to all tags that imply
- tags = db.TagDefinitions
- .Where(td => td.Name.ToLower().Contains(query))
- .Select(td => td.ObjectId)
- .ToArray();
- }
-
- int[] tagResults = SearchTags(tags);
-
- int[] mediaIds = descriptionResults
- .Union(filenameResults)
- .Union(ocrResults)
- .Union(tagResults)
- .OrderDescending()
- .ToArray();
-
- return db.Media
- .Include(m => m.Tags)
- .Include(m => m.CurrentUploadedFile)
- .Where(m => mediaIds.Contains(m.ObjectId))
- .ToArray();
- }
-
- // TODO: Make asynchronous
- private int[] SearchDescription(string query) {
- return Task.Run(() => {
- using var db = dbFactory.CreateDbContext();
- query = query.ToLower();
- return db.Media
- .Where(m =>
- (m.ShortDescription != null && m.ShortDescription.ToLower().Contains(query)) ||
- (m.LongDescription != null && m.LongDescription.ToLower().Contains(query)))
- .Select(m => m.ObjectId)
- .ToArray();
- }).GetAwaiter().GetResult();
- }
-
- // TODO: Make asynchronous
- private int[] SearchFilenames(string query) {
- return Task.Run(() => {
- using var db = dbFactory.CreateDbContext();
- query = query.ToLower();
- return db.UploadedFiles
- .Include(uf => uf.Media)
- .Where(uf => uf.Filename != null && uf.Filename.ToLower().Contains(query))
- .Select(uf => uf.Media.ObjectId)
- .Distinct()
- .ToArray();
- }).GetAwaiter().GetResult();
- }
-
- // TODO: Make asynchronous
- private int[] SearchOcr(string query) {
- return Task.Run(() => {
- using var db = dbFactory.CreateDbContext();
- query = query.ToLower();
- return db.OcrData
- .Include(o => o.Media)
- .Where(o => o.SearchableText.Contains(query))
- .Select(o => o.Media.ObjectId)
- .ToArray();
- }).GetAwaiter().GetResult();
- }
-
- // TODO: Make asynchronous
- private int[] SearchTags(int[] tags) {
- return Task.Run(() => {
- using var db = dbFactory.CreateDbContext();
- return db.Media
- .Include(m => m.Tags)
- .AsEnumerable()
- .Where(m => m.Tags.IntersectBy(tags, t => t.TagDefinitionId).Any())
- .Select(m => m.ObjectId)
- .ToArray();
- }).GetAwaiter().GetResult();
- }
-}