<refactor> * 去除ShellProgressBar组件

This commit is contained in:
nsnail 2022-12-08 09:42:31 +08:00
parent a77ba1966b
commit de2e156fa5
11 changed files with 211 additions and 324 deletions

View File

@ -4,6 +4,19 @@ namespace Dot.Color;
public static partial class Win32
{
[StructLayout(LayoutKind.Explicit)]
internal ref struct Systemtime
{
[FieldOffset(6)] public ushort wDay;
[FieldOffset(4)] public ushort wDayOfWeek;
[FieldOffset(8)] public ushort wHour;
[FieldOffset(14)] public ushort wMilliseconds;
[FieldOffset(10)] public ushort wMinute;
[FieldOffset(2)] public ushort wMonth;
[FieldOffset(12)] public ushort wSecond;
[FieldOffset(0)] public ushort wYear;
}
public delegate nint LowLevelMouseProc(int nCode, nint wParam, nint lParam);
private const string _GDI32_DLL = "gdi32.dll";
@ -35,6 +48,10 @@ public static partial class Win32
[LibraryImport(_USER32_DLL)]
internal static partial int ReleaseDC(nint hWnd, nint dc);
[LibraryImport(_KERNEL32_DLL)]
internal static partial void SetLocalTime(Systemtime st);
[LibraryImport(_USER32_DLL)]
internal static partial nint SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, nint hMod, uint dwThreadId);

View File

@ -1,4 +1,5 @@
using System.Drawing.Drawing2D;
using Size = System.Drawing.Size;
namespace Dot.Color;

128
src/FilesTool.cs Normal file
View File

@ -0,0 +1,128 @@
using System.Collections.Concurrent;
using Panel = Spectre.Console.Panel;
namespace Dot;
public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : DirOption
{
private int _breakCnt; //跳过文件数
private ProgressTask _childTask; //子任务进度
private static readonly object _lock = new(); //线程锁
private int _readCnt; //读取文件数
private int _totalCnt; //总文件数
private int _writeCnt; //写入文件数
private readonly ConcurrentDictionary<string, int> _writeStats = new(); //写入统计:后缀,数量
protected FilesTool(TOption opt) : base(opt)
{
if (!Directory.Exists(Opt.Path))
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
}
private static string[] EnumerateFiles(string path, string searchPattern)
{
var fileList = Directory
.EnumerateFiles(path, searchPattern
, new EnumerationOptions {
RecurseSubdirectories = true
, AttributesToSkip = FileAttributes.ReparsePoint
})
.Where(x => !new[] { ".git", "node_modules" }.Any(
y => x.Contains(y, StringComparison.OrdinalIgnoreCase)))
.ToArray();
return fileList;
}
protected static void CopyFile(string source, string dest)
{
try {
File.Copy(source, dest, true);
}
catch (UnauthorizedAccessException) {
File.SetAttributes(dest, new FileInfo(dest).Attributes & ~FileAttributes.ReadOnly);
File.Copy(source, dest, true);
}
}
protected FileStream CreateTempFile(out string file)
{
file = Path.Combine(Path.GetTempPath(), $"{System.Guid.NewGuid()}.tmp");
return OpenFileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
}
protected abstract ValueTask FileHandle(string file, CancellationToken cancelToken);
protected static FileStream OpenFileStream(string file, FileMode mode, FileAccess access
, FileShare share = FileShare.Read)
{
FileStream fsr = null;
try {
fsr = new FileStream(file, mode, access, share);
}
catch (UnauthorizedAccessException) {
try {
File.SetAttributes(file, new FileInfo(file).Attributes & ~FileAttributes.ReadOnly);
fsr = new FileStream(file, mode, access, share);
}
catch (Exception) {
// ignored
}
}
catch (IOException) { }
return fsr;
}
protected void ShowMessage(int readCnt, int writeCnt, int breakCnt)
{
lock (_lock) {
_readCnt += readCnt;
_writeCnt += writeCnt;
_breakCnt += breakCnt;
if (readCnt > 0) _childTask.Increment(1);
_childTask.Description
= $"{Str.Read}: [green]{_readCnt}[/]/{_totalCnt}, {Str.Write}: [red]{_writeCnt}[/], {Str.Break}: [gray]{_breakCnt}[/]";
}
}
protected void UpdateStats(string key)
{
_writeStats.AddOrUpdate(key, 1, (_, oldValue) => oldValue + 1);
}
public override async Task Run()
{
if (!Opt.WriteMode) AnsiConsole.MarkupLine("[gray]{0}[/]", Str.ExerciseMode);
IEnumerable<string> fileList;
await AnsiConsole.Progress()
.Columns(new ProgressBarColumn() //
, new ElapsedTimeColumn() //
, new PercentageColumn() //
, new SpinnerColumn() //
, new TaskDescriptionColumn { Alignment = Justify.Left } //
)
.StartAsync(async ctx => {
var taskSearchfile = ctx.AddTask(Str.SearchingFile).IsIndeterminate();
_childTask = ctx.AddTask("-/-", false);
fileList = EnumerateFiles(Opt.Path, Opt.Filter);
_totalCnt = fileList.Count();
taskSearchfile.IsIndeterminate(false);
taskSearchfile.Increment(100);
_childTask.MaxValue = _totalCnt;
_childTask.StartTask();
await Parallel.ForEachAsync(fileList, FileHandle);
});
var grid = new Grid().AddColumn(new GridColumn().NoWrap().PadRight(16))
.AddColumn(new GridColumn().Alignment(Justify.Right));
foreach (var kv in _writeStats) grid.AddRow(kv.Key, kv.Value.ToString());
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
}
}

View File

@ -1,3 +1,3 @@
global using ShellProgressBar;
global using Spectre.Console;
global using CommandLine;
global using Dot.Lang;

View File

@ -1,7 +1,6 @@
using System.Reflection;
using System.Text;
using Dot;
using Spectre.Console;
Type[] LoadVerbs()
{

View File

@ -1,57 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using NSExt.Extensions;
namespace Dot.RmBlank;
public sealed class Main : ToolBase<Option>, IDisposable
public sealed class Main : FilesTool<Option>
{
private int _breakCnt;
private bool _disposed;
private static readonly object _lockObj = new();
private int _procedCnt;
private int _replaceCnt;
private ChildProgressBar _step2Bar;
private int _totalCnt;
public Main(Option opt) : base(opt) { }
~Main()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) _step2Bar?.Dispose();
_disposed = true;
}
private async ValueTask FileHandle(string file, CancellationToken _)
{
_step2Bar.Tick();
ShowMessage(1, 0, 0);
int spacesCnt;
await using var fsrw = OpenFileStream(file, FileMode.Open, FileAccess.ReadWrite);
if (!Opt.WriteMode) { //测试,只读模式
ShowMessage(0, 1, 0);
return;
}
if (fsrw is null || fsrw.Length == 0 || (spacesCnt = GetSpacesCnt(fsrw)) == 0) {
ShowMessage(0, 0, 1);
return;
}
fsrw.Seek(0, SeekOrigin.Begin);
if (!fsrw.IsTextStream()) return;
ShowMessage(0, 1, 0);
fsrw.SetLength(fsrw.Length - spacesCnt);
}
private static int GetSpacesCnt(Stream fsr)
{
var trimLen = 0;
@ -71,43 +25,28 @@ public sealed class Main : ToolBase<Option>, IDisposable
}
private void ShowMessage(int procedCnt, int removeCnt, int breakCnt)
protected override async ValueTask FileHandle(string file, CancellationToken _)
{
lock (_lockObj) {
_procedCnt += procedCnt;
_replaceCnt += removeCnt;
_breakCnt += breakCnt;
_step2Bar.Message = string.Format(Str.ShowMessageTemp, _procedCnt, _totalCnt, _replaceCnt, _breakCnt);
ShowMessage(1, 0, 0);
int spacesCnt;
await using var fsrw = OpenFileStream(file, FileMode.Open, FileAccess.ReadWrite);
if (fsrw is null || fsrw.Length == 0 || (spacesCnt = GetSpacesCnt(fsrw)) == 0) {
ShowMessage(0, 0, 1);
return;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <inheritdoc />
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public override async Task Run()
{
if (!Directory.Exists(Opt.Path))
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
fsrw.Seek(0, SeekOrigin.Begin);
if (!fsrw.IsTextStream()) {
ShowMessage(0, 0, 1);
return;
}
using var step1Bar = new IndeterminateProgressBar(Str.SearchingFile, DefaultProgressBarOptions);
var fileList = EnumerateFiles(Opt.Path, Opt.Filter);
_totalCnt = fileList.Count();
step1Bar.Message = string.Format(Str.SearchingFileOK, _totalCnt);
step1Bar.Finished();
if (_totalCnt == 0) return;
_step2Bar = step1Bar.Spawn(_totalCnt, string.Empty, DefaultProgressBarOptions);
await Parallel.ForEachAsync(fileList, FileHandle);
if (Opt.WriteMode) fsrw.SetLength(fsrw.Length - spacesCnt);
ShowMessage(0, 1, 0);
UpdateStats(Path.GetExtension(file));
}
}

View File

@ -1,110 +1,47 @@
using System.Diagnostics.CodeAnalysis;
namespace Dot.RmBom;
public sealed class Main : ToolBase<Option>, IDisposable
public sealed class Main : FilesTool<Option>
{
private int _breakCnt;
private bool _disposed;
private static readonly object _lockObj = new();
private int _procedCnt;
private ChildProgressBar _step2Bar;
private int _totalCnt;
private int _trimCnt;
private readonly byte[] _utf8Bom = { 0xef, 0xbb, 0xbf };
private readonly byte[] _utf8Bom = { 0xef, 0xbb, 0xbf };
public Main(Option opt) : base(opt) { }
~Main()
{
Dispose(false);
}
private bool CreateTempFile(Stream fsr, string tmpFile)
private bool CloneFileWithoutBom(Stream fsr, ref string tempFile)
{
Span<byte> buffer = stackalloc byte[_utf8Bom.Length];
var readLen = fsr.Read(buffer);
if (readLen != _utf8Bom.Length || !buffer.SequenceEqual(_utf8Bom)) return false;
using var fsw = OpenFileStream(tmpFile, FileMode.OpenOrCreate, FileAccess.Write);
using var fsw = CreateTempFile(out tempFile);
int data;
while ((data = fsr.ReadByte()) != -1) fsw.WriteByte((byte)data);
return true;
}
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing) _step2Bar?.Dispose();
_disposed = true;
}
private async ValueTask FileHandle(string file, CancellationToken _)
protected override async ValueTask FileHandle(string file, CancellationToken _)
{
_step2Bar.Tick();
ShowMessage(1, 0, 0);
var tmpFile = $"{file}.tmp";
bool isReplaced;
string tmpFile = default;
await using (var fsr = OpenFileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
if (!Opt.WriteMode) { //测试,只读模式
ShowMessage(0, 1, 0);
return;
}
if (fsr is null) {
ShowMessage(0, 0, 1);
return;
}
isReplaced = CreateTempFile(fsr, tmpFile);
if (CloneFileWithoutBom(fsr, ref tmpFile)) {
if (Opt.WriteMode) CopyFile(tmpFile, file);
ShowMessage(0, 1, 0);
UpdateStats(Path.GetExtension(file));
}
else {
ShowMessage(0, 0, 1);
}
}
if (isReplaced) {
MoveFile(tmpFile, file);
ShowMessage(0, 1, 0);
}
else {
ShowMessage(0, 0, 1);
}
}
private void ShowMessage(int procedCnt, int replaceCnt, int breakCnt)
{
lock (_lockObj) {
_procedCnt += procedCnt;
_trimCnt += replaceCnt;
_breakCnt += breakCnt;
_step2Bar.Message = string.Format(Str.ShowMessageTemp, _procedCnt, _totalCnt, _trimCnt, _breakCnt);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public override async Task Run()
{
if (!Directory.Exists(Opt.Path))
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
using var step1Bar = new IndeterminateProgressBar(Str.SearchingFile, DefaultProgressBarOptions);
var fileList = EnumerateFiles(Opt.Path, Opt.Filter);
_totalCnt = fileList.Count();
step1Bar.Message = string.Format(Str.SearchingFileOK, _totalCnt);
step1Bar.Finished();
if (_totalCnt == 0) return;
_step2Bar = step1Bar.Spawn(_totalCnt, string.Empty, DefaultProgressBarOptions);
await Parallel.ForEachAsync(fileList, FileHandle);
if (tmpFile != default) File.Delete(tmpFile);
}
}

View File

@ -1,7 +1,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using Dot.Color;
namespace Dot.Time;
@ -22,23 +22,11 @@ public sealed class Main : ToolBase<Option>
, Failed
}
[StructLayout(LayoutKind.Explicit)]
private ref struct Systemtime
{
[FieldOffset(6)] public ushort wDay;
[FieldOffset(4)] public ushort wDayOfWeek;
[FieldOffset(8)] public ushort wHour;
[FieldOffset(14)] public ushort wMilliseconds;
[FieldOffset(10)] public ushort wMinute;
[FieldOffset(2)] public ushort wMonth;
[FieldOffset(12)] public ushort wSecond;
[FieldOffset(0)] public ushort wYear;
}
private const int _MAX_DEGREE_OF_PARALLELISM = 10;
private const int _NTP_PORT = 123;
private const string _OUTPUT_TEMP = "{0,-30} {1,20} {2,20}";
private static readonly object _lockObj = new();
private static readonly object _lock = new();
private int _procedCnt;
private readonly int _serverCnt;
@ -101,7 +89,7 @@ public sealed class Main : ToolBase<Option>
private static void DrawTextInConsole(int left, int top, string text)
{
lock (_lockObj) {
lock (_lock) {
Console.SetCursorPosition(left, top);
Console.Write(text);
}
@ -177,22 +165,19 @@ public sealed class Main : ToolBase<Option>
}
[DllImport("Kernel32.dll")]
private static extern void SetLocalTime(Systemtime st);
private static void SetSysteTime(DateTime time)
{
var timeToSet = new Systemtime {
wDay = (ushort)time.Day
, wDayOfWeek = (ushort)time.DayOfWeek
, wHour = (ushort)time.Hour
, wMilliseconds = (ushort)time.Millisecond
, wMinute = (ushort)time.Minute
, wMonth = (ushort)time.Month
, wSecond = (ushort)time.Second
, wYear = (ushort)time.Year
};
SetLocalTime(timeToSet);
var timeToSet = new Win32.Systemtime {
wDay = (ushort)time.Day
, wDayOfWeek = (ushort)time.DayOfWeek
, wHour = (ushort)time.Hour
, wMilliseconds = (ushort)time.Millisecond
, wMinute = (ushort)time.Minute
, wMonth = (ushort)time.Month
, wSecond = (ushort)time.Second
, wYear = (ushort)time.Year
};
Win32.SetLocalTime(timeToSet);
}
[SuppressMessage("ReSharper", "AccessToDisposedClosure")]

View File

@ -1,34 +1,17 @@
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Spectre.Console;
using Panel = Spectre.Console.Panel;
namespace Dot.ToLf;
public sealed class Main : ToolBase<Option>
public sealed class Main : FilesTool<Option>
{
private int _breakCnt;
private ProgressTask _fileTask;
private static readonly object _lockObj = new();
private long _procedCnt;
private int _replaceCnt;
private readonly ConcurrentDictionary<string, int> _replacedStats = new();
private int _totalCnt;
public Main(Option opt) : base(opt) { }
public Main(Option opt) : base(opt)
{
if (!Directory.Exists(Opt.Path))
throw new ArgumentException(nameof(Opt.Path), string.Format(Str.PathNotFound, Opt.Path));
}
private async ValueTask FileHandle(string file, CancellationToken _)
protected override async ValueTask FileHandle(string file, CancellationToken _)
{
ShowMessage(1, 0, 0);
var tmpFile = $"{file}.tmp";
var isReplaced = false;
var isBin = false;
int data;
var hasWrote = false;
var isBin = false;
string tmpFile;
int data;
await using (var fsr = OpenFileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
if (fsr is null) {
@ -37,18 +20,18 @@ public sealed class Main : ToolBase<Option>
}
await using var fsw = OpenFileStream(tmpFile, FileMode.OpenOrCreate, FileAccess.Write);
await using var fsw = CreateTempFile(out tmpFile);
while ((data = fsr.ReadByte()) != -1) {
switch (data) {
case 0x0d when fsr.ReadByte() == 0x0a: // crlf windows
fsw.WriteByte(0x0a);
isReplaced = true;
hasWrote = true;
continue;
case 0x0d: //cr macos
fsw.WriteByte(0x0a);
fsr.Seek(-1, SeekOrigin.Current);
isReplaced = true;
hasWrote = true;
continue;
case 0x00 or 0xff: //非文本文件
isBin = true;
@ -63,11 +46,10 @@ public sealed class Main : ToolBase<Option>
}
if (isReplaced && !isBin) {
if (Opt.WriteMode) //测试,只读模式
MoveFile(tmpFile, file);
if (hasWrote && !isBin) {
if (Opt.WriteMode) CopyFile(tmpFile, file);
ShowMessage(0, 1, 0);
_replacedStats.AddOrUpdate(Path.GetExtension(file), 1, (_, oldValue) => oldValue + 1);
UpdateStats(Path.GetExtension(file));
}
else {
ShowMessage(0, 0, 1);
@ -75,51 +57,4 @@ public sealed class Main : ToolBase<Option>
File.Delete(tmpFile);
}
private void ShowMessage(int procedCnt, int replaceCnt, int breakCnt)
{
lock (_lockObj) {
_procedCnt += procedCnt;
_replaceCnt += replaceCnt;
_breakCnt += breakCnt;
if (procedCnt > 0) _fileTask.Increment(1);
_fileTask.Description
= $"{Str.Read}: [green]{_procedCnt}[/]/{_totalCnt}, {Str.Write}: [red]{_replaceCnt}[/], {Str.Break}: [gray]{_breakCnt}[/]";
}
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public override async Task Run()
{
if (!Opt.WriteMode) AnsiConsole.MarkupLine("[gray]{0}[/]", Str.ExerciseMode);
IEnumerable<string> fileList;
await AnsiConsole.Progress()
.Columns(new ProgressBarColumn() //
, new ElapsedTimeColumn() //
, new PercentageColumn() //
, new SpinnerColumn() //
, new TaskDescriptionColumn { Alignment = Justify.Left } //
)
.StartAsync(async ctx => {
var taskSearchfile = ctx.AddTask(Str.SearchingFile).IsIndeterminate();
_fileTask = ctx.AddTask("-/-", false);
fileList = EnumerateFiles(Opt.Path, Opt.Filter);
_totalCnt = fileList.Count();
taskSearchfile.IsIndeterminate(false);
taskSearchfile.Increment(100);
_fileTask.MaxValue = _totalCnt;
_fileTask.StartTask();
await Parallel.ForEachAsync(fileList, FileHandle);
});
var grid = new Grid().AddColumn(new GridColumn().NoWrap().PadRight(16))
.AddColumn(new GridColumn().Alignment(Justify.Right));
foreach (var kv in _replacedStats) grid.AddRow(kv.Key, kv.Value.ToString());
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
}
}

View File

@ -5,16 +5,6 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
// ReSharper disable once StaticMemberInGenericType
private static SpinLock _spinlock;
protected readonly ProgressBarOptions //
DefaultProgressBarOptions = new() {
MessageEncodingName = "utf-8"
, ProgressBarOnBottom = true
, ForegroundColor = ConsoleColor.Yellow
, ForegroundColorDone = ConsoleColor.DarkGreen
, BackgroundColor = ConsoleColor.DarkGray
, BackgroundCharacter = '\u2500'
, ProgressCharacter = '\u2500'
};
protected TOption Opt { get; }
@ -37,18 +27,6 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
}
}
protected static IEnumerable<string> EnumerateFiles(string path, string searchPattern)
{
var fileList = Directory
.EnumerateFiles(path, searchPattern
, new EnumerationOptions {
RecurseSubdirectories = true
, AttributesToSkip = FileAttributes.ReparsePoint
})
.Where(x => !new[] { ".git", "node_modules" }.Any(
y => x.Contains(y, StringComparison.OrdinalIgnoreCase)));
return fileList;
}
protected static Task LoadingAnimate(int x, int y, out CancellationTokenSource cts)
{
@ -72,37 +50,6 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
});
}
protected static void MoveFile(string source, string dest)
{
try {
File.Move(source, dest, true);
}
catch (UnauthorizedAccessException) {
File.SetAttributes(dest, new FileInfo(dest).Attributes & ~FileAttributes.ReadOnly);
File.Move(source, dest, true);
}
}
protected static FileStream OpenFileStream(string file, FileMode mode, FileAccess access
, FileShare share = FileShare.Read)
{
FileStream fsr = null;
try {
fsr = new FileStream(file, mode, access, share);
}
catch (UnauthorizedAccessException) {
try {
File.SetAttributes(file, new FileInfo(file).Attributes & ~FileAttributes.ReadOnly);
fsr = new FileStream(file, mode, access, share);
}
catch (Exception) {
// ignored
}
}
catch (IOException) { }
return fsr;
}
public abstract Task Run();
}

View File

@ -26,7 +26,6 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1"/>
<PackageReference Include="NSExt" Version="1.0.6"/>
<PackageReference Include="ShellProgressBar" Version="5.2.0"/>
<PackageReference Include="Spectre.Console" Version="0.45.1-preview.0.46"/>
<PackageReference Include="TextCopy" Version="6.2.0"/>
</ItemGroup>