YaeBlog/YaeBlog.Core/Services/RendererService.cs

181 lines
5.4 KiB
C#
Raw Normal View History

using System.Collections.Concurrent;
using System.Diagnostics;
2024-01-23 14:33:35 +08:00
using Markdig;
2024-01-17 13:20:32 +08:00
using Microsoft.Extensions.Logging;
using YaeBlog.Core.Abstractions;
2024-01-17 13:20:32 +08:00
using YaeBlog.Core.Exceptions;
using YaeBlog.Core.Models;
2024-01-23 14:33:35 +08:00
using YamlDotNet.Core;
2024-01-19 20:33:41 +08:00
using YamlDotNet.Serialization;
2024-01-17 13:20:32 +08:00
namespace YaeBlog.Core.Services;
public class RendererService(ILogger<RendererService> logger,
EssayScanService essayScanService,
MarkdownPipeline markdownPipeline,
2024-01-19 20:33:41 +08:00
IDeserializer yamlDeserializer,
2024-01-17 13:20:32 +08:00
EssayContentService essayContentService)
{
2024-01-23 14:33:35 +08:00
private readonly Stopwatch _stopwatch = new();
private readonly List<IPreRenderProcessor> _preRenderProcessors = [];
private readonly List<IPostRenderProcessor> _postRenderProcessors = [];
2024-01-17 13:20:32 +08:00
public async Task RenderAsync()
{
2024-01-23 14:33:35 +08:00
_stopwatch.Start();
logger.LogInformation("Render essays start.");
2024-01-17 13:20:32 +08:00
List<BlogContent> contents = await essayScanService.ScanAsync();
IEnumerable<BlogContent> preProcessedContents =
PreProcess(contents);
2024-01-17 13:20:32 +08:00
2024-01-23 14:33:35 +08:00
List<BlogEssay> essays = [];
await Task.Run(() =>
{
foreach (BlogContent content in preProcessedContents)
2024-01-23 14:33:35 +08:00
{
MarkdownMetadata? metadata = TryParseMetadata(content);
BlogEssay essay = new()
{
Title = metadata?.Title ?? content.FileName,
FileName = content.FileName,
PublishTime = metadata?.Date ?? DateTime.Now,
HtmlContent = content.FileContent
};
if (metadata?.Tags is not null)
{
essay.Tags.AddRange(metadata.Tags);
}
essays.Add(essay);
}
});
ConcurrentBag<BlogEssay> postProcessEssays = [];
2024-01-23 14:33:35 +08:00
Parallel.ForEach(essays, essay =>
2024-01-17 13:20:32 +08:00
{
2024-01-19 20:33:41 +08:00
2024-01-23 14:33:35 +08:00
BlogEssay newEssay = new()
2024-01-17 13:20:32 +08:00
{
2024-01-23 14:33:35 +08:00
Title = essay.Title,
FileName = essay.FileName,
PublishTime = essay.PublishTime,
HtmlContent = Markdown.ToHtml(essay.HtmlContent, markdownPipeline)
2024-01-17 13:20:32 +08:00
};
2024-01-23 14:33:35 +08:00
newEssay.Tags.AddRange(essay.Tags);
2024-01-17 13:20:32 +08:00
postProcessEssays.Add(newEssay);
2024-01-23 14:33:35 +08:00
logger.LogDebug("Render markdown file {}.", newEssay);
2024-01-17 13:20:32 +08:00
});
2024-01-23 14:33:35 +08:00
PostProcess(postProcessEssays);
2024-01-23 14:33:35 +08:00
_stopwatch.Stop();
logger.LogInformation("Render finished, consuming {} s.",
_stopwatch.Elapsed.ToString("s\\.fff"));
2024-01-17 13:20:32 +08:00
}
2024-01-19 20:33:41 +08:00
public void AddPreRenderProcessor(IPreRenderProcessor processor)
{
bool exist = _preRenderProcessors.Any(p => p.Name == processor.Name);
if (exist)
{
throw new InvalidOperationException("There exists one pre-render processor " +
$"with the same name: {processor.Name}.");
}
_preRenderProcessors.Add(processor);
}
public void AddPostRenderProcessor(IPostRenderProcessor processor)
{
bool exist = _postRenderProcessors.Any(p => p.Name == processor.Name);
if (exist)
{
throw new InvalidCastException("There exists one post-render processor " +
$"with the same name: {processor.Name}.");
}
_postRenderProcessors.Add(processor);
}
private IEnumerable<BlogContent> PreProcess(IEnumerable<BlogContent> contents)
{
ConcurrentBag<BlogContent> processedContents = [];
Parallel.ForEach(contents, content =>
{
foreach (IPreRenderProcessor processor in _preRenderProcessors)
{
content = processor.Process(content);
}
processedContents.Add(content);
});
return processedContents;
}
private void PostProcess(IEnumerable<BlogEssay> essays)
{
Parallel.ForEach(essays, essay =>
{
foreach (IPostRenderProcessor processor in _postRenderProcessors)
{
essay = processor.Process(essay);
}
if (!essayContentService.TryAdd(essay.FileName, essay))
{
throw new BlogFileException(
$"There are two essays with the same name: '{essay.FileName}'.");
}
logger.LogDebug("Post-Process essay: {}.", essay);
});
}
2024-01-19 20:33:41 +08:00
private MarkdownMetadata? TryParseMetadata(BlogContent content)
{
string fileContent = content.FileContent.Trim();
if (!fileContent.StartsWith("---"))
{
return null;
}
// 移除起始的---
fileContent = fileContent[3..];
int lastPos = fileContent.IndexOf("---", StringComparison.Ordinal);
if (lastPos is -1 or 0)
{
return null;
}
string yamlContent = fileContent[..lastPos];
// 返回去掉元数据之后的文本
lastPos += 3;
content.FileContent = fileContent[lastPos..];
2024-01-23 14:33:35 +08:00
try
{
MarkdownMetadata metadata =
yamlDeserializer.Deserialize<MarkdownMetadata>(yamlContent);
logger.LogDebug("Title: {}, Publish Date: {}.",
metadata.Title, metadata.Date);
return metadata;
}
catch (YamlException e)
{
logger.LogWarning("Failed to parse '{}' metadata: {}", yamlContent, e);
return null;
}
2024-01-19 20:33:41 +08:00
}
2024-01-17 13:20:32 +08:00
}