using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore;
using Sakayaki.Models;
using Sakayaki.Services;
namespace Sakayaki.Pages;
public class IndexModel(SyncService syncService, ThumbnailService thumbnailService, AppDbContext dbContext, IConfiguration configuration) : PageModel
{
private static readonly HashSet<string> ImageExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"
};
private readonly SyncService _syncService = syncService;
private readonly ThumbnailService _thumbnailService = thumbnailService;
private readonly AppDbContext _dbContext = dbContext;
private readonly string? _rootPath = configuration["Fanbox:RootPath"];
private readonly string? _author = configuration["Fanbox:Author"];
[TempData]
public string? StatusMessage { get; set; }
public sealed class FolderCard
{
public required FanboxFolder Folder { get; init; }
public string? CoverUrl { get; init; }
public string? CoverFileName { get; init; }
}
public IReadOnlyList<FolderCard> Folders { get; private set; } = Array.Empty<FolderCard>();
public string? Keyword { get; private set; }
public int PageNumber { get; private set; } = 1;
public int TotalPages { get; private set; } = 1;
public int PageSize { get; } = 30;
public async Task OnGetAsync(string? keyword, int p = 1)
{
Keyword = string.IsNullOrWhiteSpace(keyword) ? null : keyword.Trim();
PageNumber = Math.Max(1, p);
var query = _dbContext.FanboxFolders.AsNoTracking();
if (!string.IsNullOrWhiteSpace(Keyword))
{
var pattern = $"%{Keyword}%";
query = query.Where(x => x.Keywords != null && EF.Functions.Like(x.Keywords, pattern));
}
var totalCount = await query.CountAsync();
TotalPages = Math.Max(1, (int)Math.Ceiling(totalCount / (double)PageSize));
if (PageNumber > TotalPages)
{
PageNumber = TotalPages;
}
var folders = await query
.OrderByDescending(x => x.Date)
.ThenByDescending(x => x.CreatedAt)
.Skip((PageNumber - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
var cards = new List<FolderCard>(folders.Count);
foreach (var folder in folders)
{
string? coverFile = null;
string? coverUrl = null;
if (!string.IsNullOrWhiteSpace(_rootPath))
{
var folderPath = Path.Combine(_rootPath, folder.FolderName);
if (Directory.Exists(folderPath))
{
coverFile = Directory.EnumerateFiles(folderPath)
.Select(Path.GetFileName)
.FirstOrDefault(name =>
!string.IsNullOrWhiteSpace(name) &&
name.StartsWith("0.", StringComparison.OrdinalIgnoreCase) &&
ImageExtensions.Contains(Path.GetExtension(name)));
}
}
if (!string.IsNullOrWhiteSpace(coverFile))
{
coverUrl = Url.Page("Index", "Image", new { id = folder.Id, file = coverFile });
}
cards.Add(new FolderCard
{
Folder = folder,
CoverUrl = coverUrl,
CoverFileName = coverFile
});
}
Folders = cards;
}
public async Task<IActionResult> OnPostUpdateAsync()
{
if (string.IsNullOrWhiteSpace(_rootPath) || string.IsNullOrWhiteSpace(_author))
{
StatusMessage = "Fanbox settings missing (RootPath/Author).";
return Page();
}
var inserted = await _syncService.SyncFanboxFoldersAsync(_rootPath, _author, HttpContext.RequestAborted);
StatusMessage = $"Inserted {inserted} rows.";
return RedirectToPage();
}
public async Task<IActionResult> OnGetEnsureThumbnailsAsync(Guid id)
{
var folder = await _dbContext.FanboxFolders.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == id);
if (folder is null)
{
return NotFound();
}
var created = await _thumbnailService.EnsureFolderThumbnailsAsync(folder.Id, folder.FolderName, HttpContext.RequestAborted);
return new JsonResult(new { created });
}
public async Task<IActionResult> OnGetImageAsync(Guid id, string file)
{
if (string.IsNullOrWhiteSpace(_rootPath) || string.IsNullOrWhiteSpace(file))
return NotFound();
var safeFile = Path.GetFileName(file);
if (string.IsNullOrWhiteSpace(safeFile))
return NotFound();
if (!ImageExtensions.Contains(Path.GetExtension(safeFile)))
return NotFound();
var folder = await _dbContext.FanboxFolders.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id == id);
if (folder is null)
return NotFound();
var filePath = Path.Combine(_rootPath, folder.FolderName, safeFile);
if (!System.IO.File.Exists(filePath))
return NotFound();
var provider = new FileExtensionContentTypeProvider();
if (!provider.TryGetContentType(filePath, out var contentType))
contentType = "application/octet-stream";
return PhysicalFile(filePath, contentType);
}
}