Newer
Older
Sakayaki / Services / SyncService.cs
@fabre fabre 15 hours ago 3 KB 基本功能
using System.Globalization;
using Microsoft.EntityFrameworkCore;
using Sakayaki.Models;

namespace Sakayaki.Services;

public sealed class SyncService(AppDbContext dbContext)
{
    private static readonly string[] DefaultKeywords =
    {
        "ユウリ","セレナ","ヒカリ","リーリエ","ミヅキ","メイ","マリィ","ルチア","コトネ",
        "大苦戦","敗北","拘束","差分","差分セット"
    };

    private readonly AppDbContext _dbContext = dbContext;

    public async Task<int> SyncFanboxFoldersAsync(
        string root,
        string author,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(root))
            throw new ArgumentException("Root path is required.", nameof(root));
        if (string.IsNullOrWhiteSpace(author))
            throw new ArgumentException("Author is required.", nameof(author));

        var inserted = 0;

        var pending = new List<FanboxFolder>();

        foreach (var dir in Directory.GetDirectories(root))
        {
            cancellationToken.ThrowIfCancellationRequested();

            var folderName = Path.GetFileName(dir);
            if (folderName.Length < 11 || folderName[10] != '-')
                continue;

            var datePart = folderName.Substring(0, 10);
            if (!DateTime.TryParseExact(
                    datePart,
                    "yyyy-MM-dd",
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.None,
                    out var date))
                continue;

            var title = folderName.Substring(11);
            var keywordsStr = BuildKeywords(title);
            var fileCount = Directory.GetFiles(dir).Length;

            var exists = await _dbContext.FanboxFolders.AsNoTracking().AnyAsync(
                x => x.Author == author && x.Date == date && x.Title == title,
                cancellationToken);
            if (exists)
                continue;

            pending.Add(new FanboxFolder
            {
                FolderName = folderName,
                Author = author,
                Date = date,
                Title = title,
                Keywords = keywordsStr,
                FileCount = fileCount
            });
        }

        if (pending.Count == 0)
            return 0;

        _dbContext.FanboxFolders.AddRange(pending);
        inserted = await _dbContext.SaveChangesAsync(cancellationToken);

        return inserted;
    }

    private static string? BuildKeywords(string title)
    {
        var hit = new HashSet<string>(StringComparer.Ordinal);

        var cleaned = title
            .Replace("【", " ")
            .Replace("】", " ")
            .Replace("(", " ")
            .Replace(")", " ")
            .Replace("(", " ")
            .Replace(")", " ")
            .Replace("/", " ")
            .Replace("/", " ")
            .Replace(",", " ")
            .Replace("_", " ")
            .Replace("-", " ");

        foreach (var w in cleaned.Split(' ', StringSplitOptions.RemoveEmptyEntries))
        {
            if (w.Length >= 2)
                hit.Add(w);
        }

        foreach (var k in DefaultKeywords)
        {
            if (title.Contains(k, StringComparison.Ordinal))
                hit.Add(k);
        }

        return hit.Count > 0 ? string.Join(",", hit) : null;
    }
}