mirror of
https://github.com/real-zony/ZonyLrcToolsX.git
synced 2025-09-06 05:36:53 +00:00
refactor: Common components are moved to the Common library.
This commit is contained in:
@@ -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}, 下载成功.");
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
65
src/ZonyLrcTools.Common/Infrastructure/IO/FileScanner.cs
Normal file
65
src/ZonyLrcTools.Common/Infrastructure/IO/FileScanner.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/ZonyLrcTools.Common/Infrastructure/IO/IFileScanner.cs
Normal file
15
src/ZonyLrcTools.Common/Infrastructure/IO/IFileScanner.cs
Normal 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);
|
||||
}
|
||||
}
|
88
src/ZonyLrcTools.Common/Infrastructure/Threading/WarpTask.cs
Normal file
88
src/ZonyLrcTools.Common/Infrastructure/Threading/WarpTask.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user