add: Scan unused and not existed image command.

This commit is contained in:
jackfiled 2024-08-25 14:52:18 +08:00
parent 9111affeec
commit 6ac162a124
5 changed files with 123 additions and 2 deletions

View File

@ -7,4 +7,6 @@ public interface IEssayScanService
public Task<BlogContents> ScanContents(); public Task<BlogContents> ScanContents();
public Task SaveBlogContent(BlogContent content, bool isDraft = true); public Task SaveBlogContent(BlogContent content, bool isDraft = true);
public Task<ImageScanResult> ScanImages();
} }

View File

@ -0,0 +1,3 @@
namespace YaeBlog.Core.Models;
public record struct ImageScanResult(List<FileInfo> UnusedImages, List<FileInfo> NotFoundImages);

View File

@ -1,4 +1,5 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using YaeBlog.Core.Abstractions; using YaeBlog.Core.Abstractions;
@ -9,7 +10,7 @@ using YamlDotNet.Serialization;
namespace YaeBlog.Core.Services; namespace YaeBlog.Core.Services;
public class EssayScanService( public partial class EssayScanService(
ISerializer yamlSerializer, ISerializer yamlSerializer,
IDeserializer yamlDeserializer, IDeserializer yamlDeserializer,
IOptions<BlogOptions> blogOptions, IOptions<BlogOptions> blogOptions,
@ -96,6 +97,79 @@ public class EssayScanService(
return contents; return contents;
} }
public async Task<ImageScanResult> ScanImages()
{
BlogContents contents = await ScanContents();
ValidateDirectory(_blogOptions.Root, out DirectoryInfo drafts, out DirectoryInfo posts);
List<FileInfo> unusedFiles = [];
List<FileInfo> notFoundFiles = [];
ImageScanResult draftResult = await ScanUnusedImagesInternal(contents.Drafts, drafts);
ImageScanResult postResult = await ScanUnusedImagesInternal(contents.Posts, posts);
unusedFiles.AddRange(draftResult.UnusedImages);
notFoundFiles.AddRange(draftResult.NotFoundImages);
unusedFiles.AddRange(postResult.UnusedImages);
notFoundFiles.AddRange(postResult.NotFoundImages);
return new ImageScanResult(unusedFiles, notFoundFiles);
}
private Task<ImageScanResult> ScanUnusedImagesInternal(IEnumerable<BlogContent> contents,
DirectoryInfo root)
{
Regex imageRegex = ImageRegex();
ConcurrentBag<FileInfo> unusedImage = [];
ConcurrentBag<FileInfo> notFoundImage = [];
Parallel.ForEach(contents, content =>
{
MatchCollection result = imageRegex.Matches(content.FileContent);
DirectoryInfo imageDirectory = new(Path.Combine(root.FullName, content.FileName));
Dictionary<string, bool> usedDictionary;
if (imageDirectory.Exists)
{
usedDictionary = (from file in imageDirectory.EnumerateFiles()
select new KeyValuePair<string, bool>(file.FullName, false)).ToDictionary();
}
else
{
usedDictionary = [];
}
foreach (Match match in result)
{
string imageName = match.Groups[1].Value;
FileInfo usedFile = imageName.Contains(content.FileName)
? new FileInfo(Path.Combine(root.FullName, imageName))
: new FileInfo(Path.Combine(root.FullName, content.FileName, imageName));
if (usedDictionary.TryGetValue(usedFile.FullName, out _))
{
usedDictionary[usedFile.FullName] = true;
}
else
{
notFoundImage.Add(usedFile);
}
}
foreach (KeyValuePair<string, bool> pair in usedDictionary.Where(p => !p.Value))
{
unusedImage.Add(new FileInfo(pair.Key));
}
});
return Task.FromResult(new ImageScanResult(unusedImage.ToList(), notFoundImage.ToList()));
}
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
private static partial Regex ImageRegex();
private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts) private void ValidateDirectory(string root, out DirectoryInfo drafts, out DirectoryInfo posts)
{ {
root = Path.Combine(Environment.CurrentDirectory, root); root = Path.Combine(Environment.CurrentDirectory, root);

View File

@ -96,7 +96,7 @@ public static class CommandExtensions
public static void AddListCommand(this RootCommand rootCommand) public static void AddListCommand(this RootCommand rootCommand)
{ {
Command command = new("list", "List all blogs"); Command command = new("list", "List all blogs");
rootCommand.Add(command); rootCommand.AddCommand(command);
command.SetHandler(async (_, _, essyScanService) => command.SetHandler(async (_, _, essyScanService) =>
{ {
@ -115,4 +115,45 @@ public static class CommandExtensions
} }
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder()); }, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
} }
public static void AddScanCommand(this RootCommand rootCommand)
{
Command command = new("scan", "Scan unused and not found images.");
rootCommand.AddCommand(command);
Option<bool> removeOption =
new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false);
command.AddOption(removeOption);
command.SetHandler(async (_, _, essayScanService, removeOptionValue) =>
{
ImageScanResult result = await essayScanService.ScanImages();
if (result.UnusedImages.Count != 0)
{
Console.WriteLine("Found unused images:");
Console.WriteLine("HINT: use '--rm' to remove unused images.");
}
foreach (FileInfo image in result.UnusedImages)
{
Console.WriteLine($" - {image.FullName}");
}
if (removeOptionValue)
{
foreach (FileInfo image in result.UnusedImages)
{
image.Delete();
}
}
Console.WriteLine("Used not existed images:");
foreach (FileInfo image in result.NotFoundImages)
{
Console.WriteLine($" - {image.FullName}");
}
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), removeOption);
}
} }

View File

@ -7,5 +7,6 @@ rootCommand.AddServeCommand();
rootCommand.AddNewCommand(); rootCommand.AddNewCommand();
rootCommand.AddListCommand(); rootCommand.AddListCommand();
rootCommand.AddWatchCommand(); rootCommand.AddWatchCommand();
rootCommand.AddScanCommand();
await rootCommand.InvokeAsync(args); await rootCommand.InvokeAsync(args);