feat: Some performance tweak.
This commit is contained in:
parent
999284b91a
commit
24fb498d59
|
@ -5,7 +5,9 @@ namespace YaeBlog.Abstraction;
|
||||||
|
|
||||||
public interface IEssayContentService
|
public interface IEssayContentService
|
||||||
{
|
{
|
||||||
public IReadOnlyDictionary<string, BlogEssay> Essays { get; }
|
public IEnumerable<BlogEssay> Essays { get; }
|
||||||
|
|
||||||
|
public int Count { get; }
|
||||||
|
|
||||||
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags { get; }
|
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags { get; }
|
||||||
|
|
||||||
|
@ -16,6 +18,8 @@ public interface IEssayContentService
|
||||||
|
|
||||||
public bool TryAdd(BlogEssay essay);
|
public bool TryAdd(BlogEssay essay);
|
||||||
|
|
||||||
|
public bool TryGetEssay(string filename, [NotNullWhen(true)] out BlogEssay? essay);
|
||||||
|
|
||||||
public void RefreshTags();
|
public void RefreshTags();
|
||||||
|
|
||||||
public void Clear();
|
public void Clear();
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@(Contents.Essays.Count)
|
@(Contents.Count)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<PageAnchor Address="@GenerateAddress(Page + 1)" Text="@($"{Page - 1}")"/>
|
<PageAnchor Address="@GenerateAddress(Page - 1)" Text="@($"{Page - 1}")"/>
|
||||||
|
|
||||||
<PageAnchor Address="@GenerateAddress(Page)" Text="@($"{Page}")" Selected="@true"/>
|
<PageAnchor Address="@GenerateAddress(Page)" Text="@($"{Page}")" Selected="@true"/>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
namespace YaeBlog.Core.Exceptions;
|
|
||||||
|
|
||||||
public sealed class ProcessInteropException : Exception
|
|
||||||
{
|
|
||||||
public ProcessInteropException(string message) : base(message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProcessInteropException(string message, Exception innerException) : base(message, innerException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace YaeBlog.Models;
|
|
||||||
|
|
||||||
public class AboutInfo
|
|
||||||
{
|
|
||||||
public required string Introduction { get; set; }
|
|
||||||
|
|
||||||
public required string Description { get; set; }
|
|
||||||
|
|
||||||
public required string AvatarImage { get; set; }
|
|
||||||
}
|
|
|
@ -7,4 +7,6 @@ public class BlogContent
|
||||||
public required MarkdownMetadata Metadata { get; init; }
|
public required MarkdownMetadata Metadata { get; init; }
|
||||||
|
|
||||||
public required string FileContent { get; set; }
|
public required string FileContent { get; set; }
|
||||||
|
|
||||||
|
public bool IsDraft { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ public class BlogEssay : IComparable<BlogEssay>
|
||||||
|
|
||||||
public required string FileName { get; init; }
|
public required string FileName { get; init; }
|
||||||
|
|
||||||
|
public required bool IsDraft { get; init; }
|
||||||
|
|
||||||
public required DateTime PublishTime { get; init; }
|
public required DateTime PublishTime { get; init; }
|
||||||
|
|
||||||
public required string Description { get; init; }
|
public required string Description { get; init; }
|
||||||
|
@ -24,6 +26,7 @@ public class BlogEssay : IComparable<BlogEssay>
|
||||||
{
|
{
|
||||||
Title = Title,
|
Title = Title,
|
||||||
FileName = FileName,
|
FileName = FileName,
|
||||||
|
IsDraft = IsDraft,
|
||||||
PublishTime = PublishTime,
|
PublishTime = PublishTime,
|
||||||
Description = Description,
|
Description = Description,
|
||||||
WordCount = WordCount,
|
WordCount = WordCount,
|
||||||
|
@ -39,10 +42,16 @@ public class BlogEssay : IComparable<BlogEssay>
|
||||||
{
|
{
|
||||||
if (other is null)
|
if (other is null)
|
||||||
{
|
{
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PublishTime.CompareTo(other.PublishTime);
|
// 草稿文章应当排在前面
|
||||||
|
if (IsDraft != other.IsDraft)
|
||||||
|
{
|
||||||
|
return IsDraft ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return other.PublishTime.CompareTo(PublishTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace YaeBlog.Models;
|
|
||||||
|
|
||||||
public class TailwindOptions
|
|
||||||
{
|
|
||||||
public const string OptionName = "Tailwind";
|
|
||||||
|
|
||||||
public required string InputFile { get; set; }
|
|
||||||
|
|
||||||
public required string OutputFile { get; set; }
|
|
||||||
}
|
|
|
@ -19,7 +19,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@foreach (IGrouping<DateTime, KeyValuePair<string, BlogEssay>> group in _essays)
|
@foreach (IGrouping<DateTime, BlogEssay> group in _essays)
|
||||||
{
|
{
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -28,9 +28,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-4 py-4 flex flex-col">
|
<div class="px-4 py-4 flex flex-col">
|
||||||
@foreach ((String name, BlogEssay essay) in group)
|
@foreach (BlogEssay essay in group)
|
||||||
{
|
{
|
||||||
<a target="_blank" href="@($"/blog/essays/{name}")">
|
<a target="_blank" href="@($"/blog/essays/{essay.FileName}")">
|
||||||
<div class="flex flex-row p-2 mx-1 rounded-lg hover:bg-gray-300">
|
<div class="flex flex-row p-2 mx-1 rounded-lg hover:bg-gray-300">
|
||||||
<div class="w-20">
|
<div class="w-20">
|
||||||
@(essay.PublishTime.ToString("MM月dd日"))
|
@(essay.PublishTime.ToString("MM月dd日"))
|
||||||
|
@ -51,14 +51,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly List<IGrouping<DateTime, KeyValuePair<string, BlogEssay>>> _essays = [];
|
private readonly List<IGrouping<DateTime, BlogEssay>> _essays = [];
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
|
|
||||||
_essays.AddRange(from essay in Contents.Essays
|
_essays.AddRange(from essay in Contents.Essays
|
||||||
orderby essay.Value.PublishTime descending
|
group essay by new DateTime(essay.PublishTime.Year, 1, 1));
|
||||||
group essay by new DateTime(essay.Value.PublishTime.Year, 1, 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@
|
||||||
<div>
|
<div>
|
||||||
<div class="grid grid-cols-4">
|
<div class="grid grid-cols-4">
|
||||||
<div class="col-span-4 md:col-span-3">
|
<div class="col-span-4 md:col-span-3">
|
||||||
@foreach (KeyValuePair<string, BlogEssay> pair in _essays)
|
@foreach (BlogEssay essay in _essays)
|
||||||
{
|
{
|
||||||
<EssayCard Essay="@(pair.Value)"/>
|
<EssayCard Essay="@(essay)"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<Pagination BaseUrl="/blog/" Page="_page" PageCount="_pageCount"/>
|
<Pagination BaseUrl="/blog/" Page="_page" PageCount="_pageCount"/>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
[SupplyParameterFromQuery] private int? Page { get; set; }
|
[SupplyParameterFromQuery] private int? Page { get; set; }
|
||||||
|
|
||||||
private readonly List<KeyValuePair<string, BlogEssay>> _essays = [];
|
private readonly List<BlogEssay> _essays = [];
|
||||||
private const int EssaysPerPage = 8;
|
private const int EssaysPerPage = 8;
|
||||||
private int _pageCount = 1;
|
private int _pageCount = 1;
|
||||||
private int _page = 1;
|
private int _page = 1;
|
||||||
|
@ -38,16 +38,15 @@
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
_page = Page ?? 1;
|
_page = Page ?? 1;
|
||||||
_pageCount = Contents.Essays.Count / EssaysPerPage + 1;
|
_pageCount = Contents.Count / EssaysPerPage + 1;
|
||||||
|
|
||||||
if (EssaysPerPage * _page > Contents.Essays.Count + EssaysPerPage)
|
if (EssaysPerPage * _page > Contents.Count + EssaysPerPage)
|
||||||
{
|
{
|
||||||
NavigationInstance.NavigateTo("/NotFount");
|
NavigationInstance.NavigateTo("/NotFount");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_essays.AddRange(Contents.Essays
|
_essays.AddRange(Contents.Essays
|
||||||
.OrderByDescending(p => p.Value.PublishTime)
|
|
||||||
.Skip((_page - 1) * EssaysPerPage)
|
.Skip((_page - 1) * EssaysPerPage)
|
||||||
.Take(EssaysPerPage));
|
.Take(EssaysPerPage));
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Contents.Essays.TryGetValue(BlogKey, out _essay))
|
if (!Contents.TryGetEssay(BlogKey, out _essay))
|
||||||
{
|
{
|
||||||
NavigationInstance.NavigateTo("/NotFound");
|
NavigationInstance.NavigateTo("/NotFound");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,12 @@ public class BlogHostedService(
|
||||||
{
|
{
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Welcome to YaeBlog!");
|
logger.LogInformation("Failed to load cache, render essays.");
|
||||||
|
|
||||||
await rendererService.RenderAsync();
|
await rendererService.RenderAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
logger.LogInformation("YaeBlog stopped!\nHave a nice day!");
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,32 @@ public sealed class BlogHotReloadService(
|
||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
logger.LogInformation("BlogHotReloadService is starting.");
|
logger.LogInformation("Hot reload is starting...");
|
||||||
|
logger.LogInformation("Change essays will lead to hot reload!");
|
||||||
|
logger.LogInformation("HINT: draft essays will be included.");
|
||||||
|
|
||||||
await rendererService.RenderAsync();
|
await rendererService.RenderAsync(true);
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
Task[] reloadTasks = [FileWatchTask(stoppingToken)];
|
||||||
|
await Task.WhenAll(reloadTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FileWatchTask(CancellationToken token)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Watching file changes...");
|
logger.LogInformation("Watching file changes...");
|
||||||
string? changFile = await watcher.WaitForChange(stoppingToken);
|
string? changeFile = await watcher.WaitForChange(token);
|
||||||
|
|
||||||
if (changFile is null)
|
if (changeFile is null)
|
||||||
{
|
{
|
||||||
logger.LogInformation("BlogHotReloadService is stopping.");
|
logger.LogInformation("File watcher is stopping.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("{} changed, re-rendering.", changFile);
|
logger.LogInformation("{} changed, re-rendering.", changeFile);
|
||||||
essayContentService.Clear();
|
essayContentService.Clear();
|
||||||
await rendererService.RenderAsync();
|
await rendererService.RenderAsync(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,28 @@ public class EssayContentService : IEssayContentService
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, BlogEssay> _essays = new();
|
private readonly ConcurrentDictionary<string, BlogEssay> _essays = new();
|
||||||
|
|
||||||
|
private readonly List<BlogEssay> _sortedEssays = [];
|
||||||
|
|
||||||
private readonly Dictionary<EssayTag, List<BlogEssay>> _tags = [];
|
private readonly Dictionary<EssayTag, List<BlogEssay>> _tags = [];
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, BlogHeadline> _headlines = new();
|
private readonly ConcurrentDictionary<string, BlogHeadline> _headlines = new();
|
||||||
|
|
||||||
public bool TryAdd(BlogEssay essay) => _essays.TryAdd(essay.FileName, essay);
|
public bool TryAdd(BlogEssay essay)
|
||||||
|
{
|
||||||
|
_sortedEssays.Add(essay);
|
||||||
|
return _essays.TryAdd(essay.FileName, essay);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryAddHeadline(string filename, BlogHeadline headline) => _headlines.TryAdd(filename, headline);
|
public bool TryAddHeadline(string filename, BlogHeadline headline) => _headlines.TryAdd(filename, headline);
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, BlogEssay> Essays => _essays;
|
public IEnumerable<BlogEssay> Essays => _sortedEssays;
|
||||||
|
|
||||||
|
public int Count => _sortedEssays.Count;
|
||||||
|
|
||||||
|
public bool TryGetEssay(string filename, [NotNullWhen(true)] out BlogEssay? essay)
|
||||||
|
{
|
||||||
|
return _essays.TryGetValue(filename, out essay);
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags => _tags;
|
public IReadOnlyDictionary<EssayTag, List<BlogEssay>> Tags => _tags;
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,8 @@ public partial class EssayScanService(
|
||||||
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
|
||||||
|
|
||||||
return new BlogContents(
|
return new BlogContents(
|
||||||
await ScanContentsInternal(drafts),
|
await ScanContentsInternal(drafts, true),
|
||||||
await ScanContentsInternal(posts));
|
await ScanContentsInternal(posts, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveBlogContent(BlogContent content, bool isDraft = true)
|
public async Task SaveBlogContent(BlogContent content, bool isDraft = true)
|
||||||
|
@ -60,7 +60,7 @@ public partial class EssayScanService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ConcurrentBag<BlogContent>> ScanContentsInternal(DirectoryInfo directory)
|
private async Task<ConcurrentBag<BlogContent>> ScanContentsInternal(DirectoryInfo directory, bool isDraft)
|
||||||
{
|
{
|
||||||
// 扫描以md结果的但是不是隐藏文件的文件
|
// 扫描以md结果的但是不是隐藏文件的文件
|
||||||
IEnumerable<FileInfo> markdownFiles = from file in directory.EnumerateFiles()
|
IEnumerable<FileInfo> markdownFiles = from file in directory.EnumerateFiles()
|
||||||
|
@ -97,7 +97,8 @@ public partial class EssayScanService(
|
||||||
|
|
||||||
contents.Add(new BlogContent
|
contents.Add(new BlogContent
|
||||||
{
|
{
|
||||||
FileName = filename[..^3], Metadata = metadata, FileContent = content[(endPos + 3)..]
|
FileName = filename[..^3], Metadata = metadata, FileContent = content[(endPos + 3)..],
|
||||||
|
IsDraft = isDraft
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (YamlException e)
|
catch (YamlException e)
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using YaeBlog.Core.Exceptions;
|
|
||||||
|
|
||||||
namespace YaeBlog.Services;
|
|
||||||
|
|
||||||
public class ProcessInteropService(ILogger<ProcessInteropService> logger)
|
|
||||||
{
|
|
||||||
public Process StartProcess(string command, string arguments)
|
|
||||||
{
|
|
||||||
string commandName;
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !command.EndsWith(".exe"))
|
|
||||||
{
|
|
||||||
commandName = command + ".exe";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
commandName = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ProcessStartInfo startInfo = new()
|
|
||||||
{
|
|
||||||
FileName = commandName,
|
|
||||||
Arguments = arguments,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
Process? process = Process.Start(startInfo);
|
|
||||||
|
|
||||||
if (process is null)
|
|
||||||
{
|
|
||||||
throw new ProcessInteropException(
|
|
||||||
$"Failed to start process: {commandName}, the return process is null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
process.OutputDataReceived += (_, data) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(data.Data))
|
|
||||||
{
|
|
||||||
logger.LogInformation("Receive output from process '{}': '{}'", commandName, data.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
process.ErrorDataReceived += (_, data) =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(data.Data))
|
|
||||||
{
|
|
||||||
logger.LogWarning("Receive error from process '{}': '{}'", commandName, data.Data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return process;
|
|
||||||
}
|
|
||||||
catch (Exception innerException)
|
|
||||||
{
|
|
||||||
throw new ProcessInteropException($"Failed to start process '{command}' with arguments '{arguments}",
|
|
||||||
innerException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,40 +21,43 @@ public partial class RendererService(
|
||||||
|
|
||||||
private readonly List<IPostRenderProcessor> _postRenderProcessors = [];
|
private readonly List<IPostRenderProcessor> _postRenderProcessors = [];
|
||||||
|
|
||||||
public async Task RenderAsync()
|
public async Task RenderAsync(bool includeDrafts = false)
|
||||||
{
|
{
|
||||||
_stopwatch.Start();
|
_stopwatch.Start();
|
||||||
logger.LogInformation("Render essays start.");
|
logger.LogInformation("Render essays start.");
|
||||||
|
|
||||||
BlogContents contents = await essayScanService.ScanContents();
|
BlogContents contents = await essayScanService.ScanContents();
|
||||||
List<BlogContent> posts = contents.Posts.ToList();
|
List<BlogContent> posts = contents.Posts.ToList();
|
||||||
|
if (includeDrafts)
|
||||||
|
{
|
||||||
|
posts.AddRange(contents.Drafts);
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<BlogContent> preProcessedContents = await PreProcess(posts);
|
IEnumerable<BlogContent> preProcessedContents = await PreProcess(posts);
|
||||||
|
|
||||||
List<BlogEssay> essays = [];
|
List<BlogEssay> essays = [];
|
||||||
await Task.Run(() =>
|
foreach (BlogContent content in preProcessedContents)
|
||||||
{
|
{
|
||||||
foreach (BlogContent content in preProcessedContents)
|
uint wordCount = GetWordCount(content);
|
||||||
|
BlogEssay essay = new()
|
||||||
{
|
{
|
||||||
uint wordCount = GetWordCount(content);
|
Title = content.Metadata.Title ?? content.FileName,
|
||||||
BlogEssay essay = new()
|
FileName = content.FileName,
|
||||||
{
|
IsDraft = content.IsDraft,
|
||||||
Title = content.Metadata.Title ?? content.FileName,
|
Description = GetDescription(content),
|
||||||
FileName = content.FileName,
|
WordCount = wordCount,
|
||||||
Description = GetDescription(content),
|
ReadTime = CalculateReadTime(wordCount),
|
||||||
WordCount = wordCount,
|
PublishTime = content.Metadata.Date ?? DateTime.Now,
|
||||||
ReadTime = CalculateReadTime(wordCount),
|
HtmlContent = content.FileContent
|
||||||
PublishTime = content.Metadata.Date ?? DateTime.Now,
|
};
|
||||||
HtmlContent = content.FileContent
|
|
||||||
};
|
|
||||||
|
|
||||||
if (content.Metadata.Tags is not null)
|
if (content.Metadata.Tags is not null)
|
||||||
{
|
{
|
||||||
essay.Tags.AddRange(content.Metadata.Tags);
|
essay.Tags.AddRange(content.Metadata.Tags);
|
||||||
}
|
|
||||||
|
|
||||||
essays.Add(essay);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
essays.Add(essay);
|
||||||
|
}
|
||||||
|
|
||||||
ConcurrentBag<BlogEssay> postProcessEssays = [];
|
ConcurrentBag<BlogEssay> postProcessEssays = [];
|
||||||
Parallel.ForEach(essays, essay =>
|
Parallel.ForEach(essays, essay =>
|
||||||
|
@ -66,7 +69,16 @@ public partial class RendererService(
|
||||||
logger.LogDebug("Render markdown file {}.", newEssay);
|
logger.LogDebug("Render markdown file {}.", newEssay);
|
||||||
});
|
});
|
||||||
|
|
||||||
await PostProcess(postProcessEssays);
|
IEnumerable<BlogEssay> postProcessedEssays = await PostProcess(postProcessEssays);
|
||||||
|
|
||||||
|
foreach (BlogEssay essay in postProcessedEssays)
|
||||||
|
{
|
||||||
|
if (!essayContentService.TryAdd(essay))
|
||||||
|
{
|
||||||
|
throw new BlogFileException($"There are at least two essays with filename '{essay.FileName}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
essayContentService.RefreshTags();
|
essayContentService.RefreshTags();
|
||||||
|
|
||||||
_stopwatch.Stop();
|
_stopwatch.Stop();
|
||||||
|
@ -117,8 +129,10 @@ public partial class RendererService(
|
||||||
return processedContents;
|
return processedContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PostProcess(IEnumerable<BlogEssay> essays)
|
private async Task<IEnumerable<BlogEssay>> PostProcess(IEnumerable<BlogEssay> essays)
|
||||||
{
|
{
|
||||||
|
ConcurrentBag<BlogEssay> processedContents = [];
|
||||||
|
|
||||||
await Parallel.ForEachAsync(essays, async (essay, _) =>
|
await Parallel.ForEachAsync(essays, async (essay, _) =>
|
||||||
{
|
{
|
||||||
foreach (IPostRenderProcessor processor in _postRenderProcessors)
|
foreach (IPostRenderProcessor processor in _postRenderProcessors)
|
||||||
|
@ -126,12 +140,13 @@ public partial class RendererService(
|
||||||
essay = await processor.ProcessAsync(essay);
|
essay = await processor.ProcessAsync(essay);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!essayContentService.TryAdd(essay))
|
processedContents.Add(essay);
|
||||||
{
|
|
||||||
throw new BlogFileException(
|
|
||||||
$"There are two essays with the same name: '{essay.FileName}'.");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
List<BlogEssay> result = processedContents.ToList();
|
||||||
|
result.Sort();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex(@"(?<!\\)[^\#\*_\-\+\`{}\[\]!~]+")]
|
[GeneratedRegex(@"(?<!\\)[^\#\*_\-\+\`{}\[\]!~]+")]
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
using System.Diagnostics;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using YaeBlog.Models;
|
|
||||||
|
|
||||||
namespace YaeBlog.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在应用程序运行的过程中启动Tailwind watch
|
|
||||||
/// 在程序退出时自动结束进程
|
|
||||||
/// 只在Development模式下启动
|
|
||||||
/// </summary>
|
|
||||||
public sealed class TailwindRefreshService(
|
|
||||||
IOptions<TailwindOptions> options,
|
|
||||||
ProcessInteropService processInteropService,
|
|
||||||
IHostEnvironment hostEnvironment,
|
|
||||||
ILogger<TailwindRefreshService> logger) : IHostedService, IDisposable
|
|
||||||
{
|
|
||||||
private Process? _tailwindProcess;
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (!hostEnvironment.IsDevelopment())
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogInformation("Try to start tailwind watcher with input {} and output {}", options.Value.InputFile,
|
|
||||||
options.Value.OutputFile);
|
|
||||||
|
|
||||||
_tailwindProcess = processInteropService.StartProcess("pnpm",
|
|
||||||
$"tailwind -i {options.Value.InputFile} -o {options.Value.OutputFile} --watch");
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_tailwindProcess?.Kill();
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_tailwindProcess?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user