Newer
Older
Sakayaki / Pages / Index.cshtml.cs
@fabre fabre 10 hours ago 5 KB 超级缩略图
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);
    }
}