diff --git a/src/Color/Win32.cs b/src/Color/Win32.cs index 0b7d9b5..a94e4eb 100644 --- a/src/Color/Win32.cs +++ b/src/Color/Win32.cs @@ -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); diff --git a/src/Color/WinInfo.cs b/src/Color/WinInfo.cs index 89978e5..3f73ec4 100644 --- a/src/Color/WinInfo.cs +++ b/src/Color/WinInfo.cs @@ -1,4 +1,5 @@ using System.Drawing.Drawing2D; +using Size = System.Drawing.Size; namespace Dot.Color; diff --git a/src/FilesTool.cs b/src/FilesTool.cs new file mode 100644 index 0000000..1433dfb --- /dev/null +++ b/src/FilesTool.cs @@ -0,0 +1,128 @@ +using System.Collections.Concurrent; +using Panel = Spectre.Console.Panel; + +namespace Dot; + +public abstract class FilesTool : ToolBase 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 _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 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)); + } +} \ No newline at end of file diff --git a/src/GlobalUsings.cs b/src/GlobalUsings.cs index 38b2f93..4f27fea 100644 --- a/src/GlobalUsings.cs +++ b/src/GlobalUsings.cs @@ -1,3 +1,3 @@ -global using ShellProgressBar; +global using Spectre.Console; global using CommandLine; global using Dot.Lang; \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs index 78b4cab..5871381 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,7 +1,6 @@ using System.Reflection; using System.Text; using Dot; -using Spectre.Console; Type[] LoadVerbs() { diff --git a/src/RmBlank/Main.cs b/src/RmBlank/Main.cs index d78471c..cddbfc7 100644 --- a/src/RmBlank/Main.cs +++ b/src/RmBlank/Main.cs @@ -1,57 +1,11 @@ -using System.Diagnostics.CodeAnalysis; using NSExt.Extensions; namespace Dot.RmBlank; -public sealed class Main : ToolBase