refactor: Common components are moved to the Common library.

This commit is contained in:
real-zony
2022-10-06 13:02:20 +08:00
parent ecab0e0f5c
commit 740e8f4c63
64 changed files with 84 additions and 150 deletions

View File

@@ -0,0 +1,52 @@
using System.Text;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
namespace ZonyLrcTools.Common.Infrastructure.Extensions
{
/// <summary>
/// 日志记录相关的扩展方法。
/// </summary>
public static class LoggerHelper
{
/// <summary>
/// 使用 <see cref="LogLevel.Warning"/> 级别打印错误日志,并记录异常堆栈。
/// </summary>
/// <param name="logger">日志记录器实例。</param>
/// <param name="errorCode">错误码,具体请参考 <see cref="ErrorCodes"/> 类的定义。</param>
/// <param name="e">异常实例,可为空。</param>
public static void LogWarningWithErrorCode(this ILogger logger, int errorCode, Exception e = null)
{
logger.LogWarning($"错误代码: {errorCode}\n堆栈异常: {e?.StackTrace}");
}
/// <summary>
/// 使用 <see cref="LogLevel.Warning"/> 级别打印错误日志,并记录异常堆栈。
/// </summary>
/// <param name="logger">日志记录器的实例。</param>
/// <param name="exception">错误码异常实例。</param>
public static void LogWarningInfo(this ILogger logger, ErrorCodeException exception)
{
if (exception.ErrorCode < 50000)
{
throw exception;
}
var sb = new StringBuilder();
sb.Append($"错误代码: {exception.ErrorCode},信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}");
sb.Append($"\n附加信息:\n {JsonConvert.SerializeObject(exception.AttachObject)}");
logger.LogWarning(sb.ToString());
}
/// <summary>
/// 使用 <see cref="LogLevel.Information"/> 级别打印歌曲下载成功信息。
/// </summary>
/// <param name="logger">日志记录器的实例。</param>
/// <param name="musicInfo">需要打印的歌曲信息。</param>
public static void LogSuccessful(this ILogger logger, MusicInfo musicInfo)
{
logger.LogInformation($"歌曲名: {musicInfo.Name}, 艺术家: {musicInfo.Artist}, 下载成功.");
}
}
}

View File

@@ -0,0 +1,24 @@
namespace ZonyLrcTools.Common.Infrastructure.Extensions
{
/// <summary>
/// 字符串处理相关的工具方法。
/// </summary>
public static class StringHelper
{
/// <summary>
/// 截断指定字符串末尾的匹配字串。
/// </summary>
/// <param name="string">待截断的字符串。</param>
/// <param name="trimEndStr">需要在末尾截断的字符串。</param>
/// <returns>截断成功的字符串实例。</returns>
public static string TrimEnd(this string @string, string trimEndStr)
{
if (@string.EndsWith(trimEndStr, StringComparison.Ordinal))
{
return @string.Substring(0, @string.Length - trimEndStr.Length);
}
return @string;
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Exceptions;
using ZonyLrcTools.Common.Infrastructure.Extensions;
namespace ZonyLrcTools.Common.Infrastructure.IO
{
public class FileScanner : IFileScanner, ITransientDependency
{
public ILogger<FileScanner> Logger { get; set; }
public FileScanner()
{
Logger = NullLogger<FileScanner>.Instance;
}
public Task<List<FileScannerResult>> ScanAsync(string path, IEnumerable<string> extensions)
{
if (extensions == null || !extensions.Any())
{
throw new ErrorCodeException(ErrorCodes.FileSuffixIsEmpty);
}
if (!Directory.Exists(path))
{
throw new ErrorCodeException(ErrorCodes.DirectoryNotExist);
}
var files = new List<FileScannerResult>();
foreach (var extension in extensions)
{
var tempResult = new ConcurrentBag<string>();
SearchFile(tempResult, path, extension);
files.Add(new FileScannerResult(
Path.GetExtension(extension) ?? throw new ErrorCodeException(ErrorCodes.UnableToGetTheFileExtension),
tempResult.ToList()));
}
return Task.FromResult(files);
}
private void SearchFile(ConcurrentBag<string> files, string folder, string extension)
{
try
{
foreach (var file in Directory.GetFiles(folder, extension))
{
files.Add(file);
}
foreach (var directory in Directory.GetDirectories(folder))
{
SearchFile(files, directory, extension);
}
}
catch (Exception e)
{
Logger.LogWarningWithErrorCode(ErrorCodes.ScanFileError, e);
}
}
}
}

View File

@@ -0,0 +1,29 @@
namespace ZonyLrcTools.Common.Infrastructure.IO
{
/// <summary>
/// 文件扫描结果对象。
/// </summary>
public class FileScannerResult
{
/// <summary>
/// 当前路径对应的扩展名。
/// </summary>
public string ExtensionName { get; }
/// <summary>
/// 当前扩展名下面的所有文件路径集合。
/// </summary>
public List<string> FilePaths { get; }
/// <summary>
/// 构造一个新的 <see cref="FileScannerResult"/> 对象。
/// </summary>
/// <param name="extensionName">当前路径对应的扩展名。</param>
/// <param name="filePaths">当前扩展名下面的所有文件路径集合。</param>
public FileScannerResult(string extensionName, List<string> filePaths)
{
ExtensionName = extensionName;
FilePaths = filePaths;
}
}
}

View File

@@ -0,0 +1,38 @@
namespace ZonyLrcTools.Common.Infrastructure.IO
{
public static class FileStreamExtensions
{
/// <summary>
/// 将字节数据通过缓冲区的形式,写入到文件当中。
/// </summary>
/// <param name="fileStream">需要写入数据的文件流。</param>
/// <param name="data">等待写入的数据。</param>
/// <param name="bufferSize">缓冲区大小。</param>
public static async Task WriteBytesToFileAsync(this FileStream fileStream, byte[] data, int bufferSize = 1024)
{
await using (fileStream)
{
var count = data.Length / 1024;
var modCount = data.Length % 1024;
if (count <= 0)
{
await fileStream.WriteAsync(data, 0, modCount);
}
else
{
for (var i = 0; i < count; i++)
{
await fileStream.WriteAsync(data, i * 1024, 1024);
}
if (modCount != 0)
{
await fileStream.WriteAsync(data, count * 1024, modCount);
}
}
await fileStream.FlushAsync();
}
}
}
}

View File

@@ -0,0 +1,15 @@
namespace ZonyLrcTools.Common.Infrastructure.IO
{
/// <summary>
/// 音乐文件扫描器,用于扫描音乐文件。
/// </summary>
public interface IFileScanner
{
/// <summary>
/// 扫描指定路径下面的歌曲文件。
/// </summary>
/// <param name="path">等待扫描的路径。</param>
/// <param name="extensions">需要搜索的歌曲后缀名。</param>
Task<List<FileScannerResult>> ScanAsync(string path, IEnumerable<string> extensions);
}
}

View File

@@ -0,0 +1,88 @@
namespace ZonyLrcTools.Common.Infrastructure.Threading
{
/// <summary>
/// 针对 Task 的包装类,基于信号量 <see cref="SemaphoreSlim"/> 限定并行度。
/// </summary>
public class WarpTask : IDisposable
{
private readonly CancellationTokenSource _cts = new();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxDegreeOfParallelism;
public WarpTask(int maxDegreeOfParallelism)
{
if (maxDegreeOfParallelism <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism));
}
_maxDegreeOfParallelism = maxDegreeOfParallelism;
_semaphore = new SemaphoreSlim(maxDegreeOfParallelism);
}
public async Task RunAsync(Func<Task> taskFactory, CancellationToken cancellationToken = default)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token))
{
await _semaphore.WaitAsync(cts.Token);
try
{
await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
}
public async Task<T> RunAsync<T>(Func<Task<T>> taskFactory, CancellationToken cancellationToken = default)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cts.Token))
{
await _semaphore.WaitAsync(cts.Token);
try
{
return await taskFactory().ConfigureAwait(false);
}
finally
{
_semaphore.Release(1);
}
}
}
private bool _disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
_cts.Cancel();
for (int i = 0; i < _maxDegreeOfParallelism; i++)
{
_semaphore.WaitAsync().GetAwaiter().GetResult();
}
_semaphore.Dispose();
_cts.Dispose();
}
_disposedValue = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~WarpTask()
{
Dispose(false);
}
}
}