<refactor> ui美化...

This commit is contained in:
2022-12-08 16:42:59 +08:00
parent f3d250ae87
commit 0b4e582bbd
20 changed files with 468 additions and 321 deletions

View File

@ -0,0 +1,17 @@
Hotkey,^d,start
return
start:
loop,100{
send,{AppsKey}
Sleep,100
send,{Up}
Sleep,100
send,{Up}
Sleep,100
Send,{Enter}
Sleep,500
Send,{Esc},
Sleep,100
Send,{Down}
Sleep,100
}

View File

@ -5,7 +5,7 @@ public sealed class Main : ToolBase<Option>
{
public Main(Option opt) : base(opt) { }
public override Task Run()
protected override Task Core()
{
Application.Run(new WinMain());
return Task.CompletedTask;

View File

@ -45,6 +45,39 @@ public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : Dir
return fileList;
}
protected override async Task Core()
{
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, out _excludeCnt);
_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.OrderByDescending(x => x.Value).ThenBy(x => x.Key))
grid.AddRow(kv.Key, kv.Value.ToString());
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
}
protected FileStream CreateTempFile(out string file)
{
@ -94,37 +127,4 @@ public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : Dir
{
_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, out _excludeCnt);
_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.OrderByDescending(x => x.Value).ThenBy(x => x.Key))
grid.AddRow(kv.Key, kv.Value.ToString());
AnsiConsole.Write(new Panel(grid).Header(Str.WriteFileStats));
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text;
using NSExt.Extensions;
@ -6,13 +7,9 @@ namespace Dot.Git;
public class Main : ToolBase<Option>
{
private const int _POS_Y_MSG = 74; //git command rsp 显示的位置 y
private const int _POST_Y_LOADING = 70; //loading 动画显示的位置 y
private const int _REP_PATH_LENGTH_LIMIT = 32; //仓库路径长度显示截断阈值
private (int x, int y) _cursorPosBackup; //光标位置备份
private readonly Encoding _gitOutputEnc; //git command rsp 编码
private List<string> _repoPathList; //仓库目录列表
private ConcurrentDictionary<string, StringBuilder> _repoRsp; //仓库信息容器
private ConcurrentDictionary<string, TaskStatusColumn.Statues> _repoStatus;
public Main(Option opt) : base(opt)
{
@ -23,25 +20,24 @@ public class Main : ToolBase<Option>
}
private async ValueTask DirHandle(string dir, CancellationToken cancelToken)
private async ValueTask DirHandle(KeyValuePair<string, ProgressTask> payload, CancellationToken _)
{
var row = _repoPathList.FindIndex(x => x == dir); // 行号
var tAnimate = LoadingAnimate(_POST_Y_LOADING, _cursorPosBackup.y + row, out var cts);
payload.Value.StartTask();
payload.Value.State.Status(TaskStatusColumn.Statues.Executing);
// 打印 git command rsp
void ExecRspReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data is null) return;
var msg = Encoding.UTF8.GetString(_gitOutputEnc.GetBytes(e.Data));
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, new string(' ', Console.WindowWidth - _POS_Y_MSG));
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, msg);
_repoRsp[payload.Key].Append(msg.EscapeMarkup());
}
// 启动git进程
{
var startInfo = new ProcessStartInfo {
CreateNoWindow = true
, WorkingDirectory = dir
, WorkingDirectory = payload.Key
, FileName = "git"
, Arguments = Opt.Args
, UseShellExecute = false
@ -54,58 +50,78 @@ public class Main : ToolBase<Option>
p.BeginOutputReadLine();
p.BeginErrorReadLine();
await p.WaitForExitAsync();
}
cts.Cancel();
await tAnimate;
cts.Dispose();
payload.Value.IsIndeterminate(false);
if (p.ExitCode == 0) {
payload.Value.State.Status(TaskStatusColumn.Statues.Succeed);
_repoStatus.AddOrUpdate(payload.Key, _ => TaskStatusColumn.Statues.Succeed
, (_, _) => TaskStatusColumn.Statues.Succeed);
payload.Value.Increment(100);
}
else {
payload.Value.State.Status(TaskStatusColumn.Statues.Failed);
_repoStatus.AddOrUpdate(payload.Key, _ => TaskStatusColumn.Statues.Failed
, (_, _) => TaskStatusColumn.Statues.Failed);
payload.Value.Increment(0);
}
}
private void StashCurorPos()
{
_cursorPosBackup = Console.GetCursorPosition();
}
public override async Task Run()
protected override async Task Core()
{
// 查找git仓库目录
{
Console.Write(Str.FindGitReps, Opt.Path);
StashCurorPos();
var tAnimate = LoadingAnimate(_cursorPosBackup.x, _cursorPosBackup.y, out var cts);
_repoPathList = Directory.GetDirectories(Opt.Path, ".git" //
var progressBar = new ProgressBarColumn { Width = 10 };
await AnsiConsole.Progress()
.Columns(progressBar //
, new ElapsedTimeColumn() //
, new SpinnerColumn() //
, new TaskStatusColumn() //
, new TaskDescriptionColumn { Alignment = Justify.Left } //
)
.StartAsync(async ctx => {
var taskFinder = ctx.AddTask(string.Format(Str.FindGitReps, Opt.Path)).IsIndeterminate();
var paths = Directory.GetDirectories(Opt.Path, ".git" //
, new EnumerationOptions //
{
MaxRecursionDepth = Opt.MaxRecursionDepth
, RecurseSubdirectories = true
, IgnoreInaccessible = true
, AttributesToSkip = FileAttributes.ReparsePoint
, AttributesToSkip
= FileAttributes.ReparsePoint
})
.Select(x => Directory.GetParent(x)!.FullName)
.ToList();
cts.Cancel();
await tAnimate;
cts.Dispose();
.Select(x => Directory.GetParent(x)!.FullName);
_repoRsp = new ConcurrentDictionary<string, StringBuilder>();
_repoStatus = new ConcurrentDictionary<string, TaskStatusColumn.Statues>();
var tasks = new Dictionary<string, ProgressTask>();
foreach (var path in paths) {
_repoRsp.TryAdd(path, new StringBuilder());
_repoStatus.TryAdd(path, default);
var task = ctx.AddTask(new DirectoryInfo(path).Name, false).IsIndeterminate();
tasks.Add(path, task);
}
// 打印git仓库目录
{
Console.WriteLine(Str.Ok);
StashCurorPos();
taskFinder.IsIndeterminate(false);
taskFinder.Increment(100);
taskFinder.State.Status(TaskStatusColumn.Statues.Succeed);
var i = 0;
Console.WriteLine( //
string.Join(Environment.NewLine
, _repoPathList.Select(
x => $"{++i}: {new DirectoryInfo(x).Name.Sub(0, _REP_PATH_LENGTH_LIMIT)}"))
//
);
await Parallel.ForEachAsync(tasks, DirHandle);
});
var table = new Table().AddColumn(new TableColumn(Str.Repository) { Width = 50 })
.AddColumn(new TableColumn(Str.Command))
.AddColumn(new TableColumn(Str.Response) { Width = 50 })
.Caption(
$"{Str.ZeroCode}: [green]{_repoStatus.Count(x => x.Value == TaskStatusColumn.Statues
.Succeed)}[/]/{_repoStatus.Count}");
foreach (var repo in _repoRsp) {
var status = _repoStatus[repo.Key].Desc();
table.AddRow(status.Replace(_repoStatus[repo.Key].ToString(), new DirectoryInfo(repo.Key).Name), Opt.Args
, status.Replace(_repoStatus[repo.Key].ToString(), repo.Value.ToString()));
}
// 并行执行git命令
await Parallel.ForEachAsync(_repoPathList, DirHandle);
Console.SetCursorPosition(_cursorPosBackup.x, _cursorPosBackup.y + _repoPathList.Count);
AnsiConsole.Write(table);
}
}

View File

@ -0,0 +1,14 @@
namespace Dot.Git;
public static class ProgressTaskStateExtensions
{
public static TaskStatusColumn.Statues Status(this ProgressTaskState me)
{
return me.Get<TaskStatusColumn.Statues>(nameof(TaskStatusColumn));
}
public static void Status(this ProgressTaskState me, TaskStatusColumn.Statues value)
{
me.Update<TaskStatusColumn.Statues>(nameof(TaskStatusColumn), _ => value);
}
}

View File

@ -0,0 +1,35 @@
using System.ComponentModel;
using NSExt.Extensions;
using Spectre.Console.Rendering;
namespace Dot.Git;
public class TaskStatusColumn : ProgressColumn
{
public enum Statues : byte
{
[Description($"[gray]{nameof(Ready)}[/]")]
Ready
, [Description($"[yellow]{nameof(Executing)}[/]")]
Executing
, [Description($"[green]{nameof(Succeed)}[/]")]
Succeed
, [Description($"[red]{nameof(Failed)}[/]")]
Failed
}
/// <summary>
/// Gets or sets the alignment of the task description.
/// </summary>
public Justify Alignment { get; set; } = Justify.Right;
/// <inheritdoc />
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
var text = task.State.Get<Statues>(nameof(TaskStatusColumn));
return new Markup(text.Desc()).Overflow(Overflow.Ellipsis).Justify(Alignment);
}
}

View File

@ -7,7 +7,7 @@ public sealed class Main : ToolBase<Option>
public Main(Option opt) : base(opt) { }
public override Task Run()
protected override Task Core()
{
var guid = System.Guid.NewGuid().ToString();
if (Opt.Upper) guid = guid.ToUpper();

View File

@ -8,7 +8,7 @@ public sealed class Main : ToolBase<Option>
{
public Main(Option opt) : base(opt) { }
public override async Task Run()
protected override async Task Core()
{
foreach (var item in NetworkInterface.GetAllNetworkInterfaces()) {
if (item.NetworkInterfaceType != NetworkInterfaceType.Ethernet ||

View File

@ -53,7 +53,7 @@ public class Main : ToolBase<Option>
return text.Replace("\\\"", "\"");
}
public override async Task Run()
protected override async Task Core()
{
string result = null;
if (Opt.Compress)

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root" xmlns="">
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
id="root" xmlns="">
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
</xsd:schema>
<resheader name="resmimetype">
@ -130,7 +131,7 @@
<data name="KeepSession" xml:space="preserve">
<value>Keep the session after executing the command</value>
</data>
<data name="NtpServerTime" xml:space="preserve">
<data name="NtpClock" xml:space="preserve">
<value>NTP server standard clock: {0}</value>
</data>
<data name="GitTool" xml:space="preserve">
@ -187,4 +188,25 @@
<data name="WriteFileStats" xml:space="preserve">
<value>Write statistics</value>
</data>
<data name="LocalClock" xml:space="preserve">
<value>local clock</value>
</data>
<data name="ExcludePathRegexes" xml:space="preserve">
<value>Regular expression to exclude paths</value>
</data>
<data name="Exclude" xml:space="preserve">
<value>exclude</value>
</data>
<data name="Repository" xml:space="preserve">
<value>storehouse</value>
</data>
<data name="Command" xml:space="preserve">
<value>Order</value>
</data>
<data name="Response" xml:space="preserve">
<value>response</value>
</data>
<data name="ZeroCode" xml:space="preserve">
<value>Git exit code is zero</value>
</data>
</root>

View File

@ -82,6 +82,9 @@
<data name="SyncToLocalTime" xml:space="preserve">
<value>同步本机时间</value>
</data>
<data name="LocalClock" xml:space="preserve">
<value>本机时钟</value>
</data>
<data name="ServerTime" xml:space="preserve">
<value>同步本机时间</value>
</data>
@ -161,8 +164,8 @@
<data name="LocalTimeOffset" xml:space="preserve">
<value>{0}, 本机时钟偏移: {1} ms</value>
</data>
<data name="NtpServerTime" xml:space="preserve">
<value>NTP 服务器标准时钟: {0}</value>
<data name="NtpClock" xml:space="preserve">
<value>NTP 标准时钟</value>
</data>
<data name="LocalTimeSyncDone" xml:space="preserve">
<value>本机时间已同步</value>
@ -186,7 +189,7 @@
<value>OK</value>
</data>
<data name="FindGitReps" xml:space="preserve">
<value>查找 "{0}" 下所有git仓库目录... </value>
<value>查找 "{0}" 下所有git仓库目录 </value>
</data>
<data name="ExerciseMode" xml:space="preserve">
<value>只读模式, 不会真实修改文件!</value>
@ -205,4 +208,16 @@
<data name="WriteFileStats" xml:space="preserve">
<value>写入统计</value>
</data>
<data name="Repository" xml:space="preserve">
<value>仓库</value>
</data>
<data name="Command" xml:space="preserve">
<value>命令</value>
</data>
<data name="Response" xml:space="preserve">
<value>响应</value>
</data>
<data name="ZeroCode" xml:space="preserve">
<value>Git退出码为零的</value>
</data>
</root>

View File

@ -14,13 +14,8 @@ Type[] LoadVerbs()
async Task Run(object args)
{
if (args is not OptionBase option) return;
var tool = ToolsFactory.Create(option);
await tool.Run();
if (option!.KeepSession) {
AnsiConsole.MarkupLine(Str.PressAnyKey);
AnsiConsole.Console.Input.ReadKey(true);
}
}

View File

@ -15,7 +15,7 @@ public sealed class Main : ToolBase<Option>
public Main(Option opt) : base(opt) { }
public override Task Run()
protected override Task Core()
{
unsafe {
var pSource = stackalloc char[_charTable.Sum(x => x.Length)];

View File

@ -99,7 +99,7 @@ html-decode: {o.HtmlDecode}
Console.WriteLine(outputTemp);
}
public override async Task Run()
protected override async Task Core()
{
if (Opt.Text.NullOrEmpty()) Opt.Text = await ClipboardService.GetTextAsync();
if (Opt.Text.NullOrEmpty()) throw new ArgumentException(Str.InputTextIsEmpty);

View File

@ -1,35 +1,13 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
namespace Dot.Time;
public sealed class Main : ToolBase<Option>
{
private record Server
{
public int ConsoleRowIndex;
public TimeSpan Offset;
public ServerStatues Status;
}
private enum ServerStatues : byte
{
Ready
, Connecting
, Succeed
, Failed
}
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 _lock = new();
private int _procedCnt;
private readonly int _serverCnt;
private readonly string[] _srvAddr = {
private readonly string[] _ntpServers = {
"ntp.ntsc.ac.cn", "cn.ntp.org.cn", "edu.ntp.org.cn", "cn.pool.ntp.org"
, "time.pool.aliyun.com", "time1.aliyun.com", "time2.aliyun.com"
, "time3.aliyun.com", "time4.aliyun.com", "time5.aliyun.com"
@ -39,60 +17,21 @@ public sealed class Main : ToolBase<Option>
, "ntp.neu.edu.cn", "ntp.bupt.edu.cn", "ntp.shu.edu.cn", "pool.ntp.org"
, "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", "3.pool.ntp.org"
, "asia.pool.ntp.org", "time1.google.com", "time2.google.com"
, "time3.google.com", "time4.google.com", "time.apple.com", "time1.apple.com"
, "time2.apple.com", "time3.apple.com", "time4.apple.com", "time5.apple.com"
, "time6.apple.com", "time7.apple.com", "time.windows.com", "time.nist.gov"
, "time-nw.nist.gov", "time-a.nist.gov", "time-b.nist.gov", "stdtime.gov.hk"
, "time3.google.com", "time4.google.com", "time.apple.com"
, "time1.apple.com", "time2.apple.com", "time3.apple.com"
, "time4.apple.com", "time5.apple.com", "time6.apple.com"
, "time7.apple.com", "time.windows.com", "time.nist.gov"
, "time-nw.nist.gov", "time-a.nist.gov", "time-b.nist.gov"
, "stdtime.gov.hk"
};
private readonly Dictionary<string, Server> _srvStatus;
private double _offsetAvg;
private int _successCnt;
public Main(Option opt) : base(opt)
{
_serverCnt = _srvAddr.Length;
var i = 0;
_srvStatus = _srvAddr.ToDictionary(
x => x, _ => new Server { Status = ServerStatues.Ready, ConsoleRowIndex = ++i });
}
private static void ChangeStatus(KeyValuePair<string, Server> server, ServerStatues status
, TimeSpan offset = default)
{
server.Value.Status = status;
server.Value.Offset = offset;
DrawTextInConsole(0, server.Value.ConsoleRowIndex
, string.Format(_OUTPUT_TEMP, server.Key, server.Value.Status
, status == ServerStatues.Succeed ? server.Value.Offset : string.Empty));
}
private async Task DrawLoading()
{
char[] loading = { '-', '\\', '|', '/' };
var loadingIndex = 0;
while (true) {
if (Volatile.Read(ref _procedCnt) == _serverCnt) break;
await Task.Delay(100);
++loadingIndex;
for (var i = 0; i != _serverCnt; ++i)
DrawTextInConsole(
34, i + 1
, _srvStatus[_srvAddr[i]].Status is ServerStatues.Succeed or ServerStatues.Failed
? " "
: loading[loadingIndex % 4].ToString());
}
Debug.WriteLine(Environment.CurrentManagedThreadId + ":" + DateTime.Now.ToString("O"));
}
private static void DrawTextInConsole(int left, int top, string text)
{
lock (_lock) {
Console.SetCursorPosition(left, top);
Console.Write(text);
}
}
public Main(Option opt) : base(opt) { }
private TimeSpan GetNtpOffset(string server)
@ -132,32 +71,20 @@ public sealed class Main : ToolBase<Option>
}
}
private void PrintTemplate()
private ValueTask ServerHandle(KeyValuePair<string, ProgressTask> payload, CancellationToken _)
{
Console.Clear();
Console.CursorVisible = false;
var row = //
_srvStatus.Select(x //
=> string.Format(_OUTPUT_TEMP, x.Key, x.Value.Status
, x.Value.Offset == TimeSpan.Zero ? string.Empty : x.Value.Offset));
Console.WriteLine(_OUTPUT_TEMP, Str.Server, Str.Status, Str.LocalClockOffset);
Console.WriteLine(string.Join(Environment.NewLine, row));
}
private ValueTask ServerHandle(KeyValuePair<string, Server> server)
{
ChangeStatus(server, ServerStatues.Connecting);
var offset = GetNtpOffset(server.Key);
Interlocked.Increment(ref _procedCnt);
payload.Value.StartTask();
payload.Value.State.Status(TaskStatusColumn.Statues.Connecting);
var offset = GetNtpOffset(payload.Key);
if (offset == TimeSpan.Zero) {
ChangeStatus(server, ServerStatues.Failed);
payload.Value.State.Status(TaskStatusColumn.Statues.Failed);
payload.Value.IsIndeterminate(false).Increment(0);
}
else {
payload.Value.State.Status(TaskStatusColumn.Statues.Succeed);
payload.Value.State.Result(offset);
Interlocked.Increment(ref _successCnt);
ChangeStatus(server, ServerStatues.Succeed, offset);
payload.Value.IsIndeterminate(false).Increment(100);
}
return ValueTask.CompletedTask;
@ -179,46 +106,67 @@ public sealed class Main : ToolBase<Option>
Win32.SetLocalTime(timeToSet);
}
[SuppressMessage("ReSharper", "AccessToDisposedClosure")]
protected override async Task Core()
{
await AnsiConsole.Progress()
.Columns(new TaskDescriptionColumn() //
, new ProgressBarColumn() //
, new ElapsedTimeColumn() //
, new SpinnerColumn() //
, new TaskStatusColumn() //
, new TaskResultColumn())
.StartAsync(async ctx => {
var tasks = new Dictionary<string, ProgressTask>();
foreach (var server in _ntpServers) {
var t = ctx.AddTask(server, false).IsIndeterminate();
tasks.Add(server, t);
}
await Parallel.ForEachAsync(
tasks, new ParallelOptions { MaxDegreeOfParallelism = _MAX_DEGREE_OF_PARALLELISM }
, ServerHandle);
_offsetAvg = tasks.Where(x => x.Value.State.Status() == TaskStatusColumn.Statues.Succeed)
.Average(x => x.Value.State.Result().TotalMilliseconds);
});
AnsiConsole.MarkupLine(Str.NtpReceiveDone, $"[green]{_successCnt}[/]", _ntpServers.Length
, $"[yellow]{_offsetAvg:f2}[/]");
if (Opt.Sync) {
SetSysteTime(DateTime.Now.AddMilliseconds(-_offsetAvg));
AnsiConsole.MarkupLine($"[green]{Str.LocalTimeSyncDone}[/]");
}
}
public override async Task Run()
{
PrintTemplate();
var tLoading = DrawLoading();
await Core();
if (Opt.KeepSession) {
var table = new Table().HideHeaders()
.AddColumn(new TableColumn(string.Empty))
.AddColumn(new TableColumn(string.Empty))
.Caption(Str.PressAnyKey)
.AddRow(Str.NtpClock, DateTime.Now.AddMilliseconds(-_offsetAvg).ToString("O"))
.AddRow(Str.LocalClock, DateTime.Now.ToString("O"));
await Parallel.ForEachAsync(_srvStatus
, new ParallelOptions { MaxDegreeOfParallelism = _MAX_DEGREE_OF_PARALLELISM }
, (server, _) => ServerHandle(server));
await tLoading;
var avgOffset = TimeSpan.FromTicks((long)_srvStatus //
.Where(x => x.Value.Status == ServerStatues.Succeed)
.Average(x => x.Value.Offset.Ticks));
Console.SetCursorPosition(0, _serverCnt + 1);
Console.WriteLine(Str.NtpReceiveDone, _successCnt, _serverCnt, avgOffset.TotalMilliseconds);
if (!Opt.Sync) {
if (!Opt.KeepSession) return;
var waitObj = new ManualResetEvent(false);
var _ = Task.Run(async () => {
var top = Console.GetCursorPosition().Top;
while (true) {
Console.SetCursorPosition(0, top);
Console.Write(Str.NtpServerTime, (DateTime.Now - avgOffset).ToString("O"));
waitObj.Set();
var cts = new CancellationTokenSource();
var task = AnsiConsole.Live(table)
.StartAsync(async ctx => {
while (!cts.IsCancellationRequested) {
ctx.UpdateTarget(
table.UpdateCell(
0, 1, DateTime.Now.AddMilliseconds(-_offsetAvg).ToString("O"))
.UpdateCell(1, 1, DateTime.Now.ToString("O")));
await Task.Delay(100);
}
// ReSharper disable once FunctionNeverReturns
});
waitObj.WaitOne();
return;
}
SetSysteTime(DateTime.Now - avgOffset);
Console.WriteLine(Str.LocalTimeSyncDone);
await AnsiConsole.Console.Input.ReadKeyAsync(true, cts.Token);
cts.Cancel();
await task;
}
}
}

View File

@ -0,0 +1,24 @@
namespace Dot.Time;
public static class ProgressTaskStateExtensions
{
public static TimeSpan Result(this ProgressTaskState me)
{
return me.Get<TimeSpan>(nameof(TaskResultColumn));
}
public static void Result(this ProgressTaskState me, TimeSpan value)
{
me.Update<TimeSpan>(nameof(TaskResultColumn), _ => value);
}
public static TaskStatusColumn.Statues Status(this ProgressTaskState me)
{
return me.Get<TaskStatusColumn.Statues>(nameof(TaskStatusColumn));
}
public static void Status(this ProgressTaskState me, TaskStatusColumn.Statues value)
{
me.Update<TaskStatusColumn.Statues>(nameof(TaskStatusColumn), _ => value);
}
}

View File

@ -0,0 +1,18 @@
using Spectre.Console.Rendering;
namespace Dot.Time;
public class TaskResultColumn : ProgressColumn
{
/// <summary>
/// Gets or sets the alignment of the task description.
/// </summary>
public Justify Alignment { get; set; } = Justify.Right;
/// <inheritdoc />
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
var text = task.State.Get<TimeSpan>(nameof(TaskResultColumn));
return new Markup(text.ToString()).Overflow(Overflow.Ellipsis).Justify(Alignment);
}
}

View File

@ -0,0 +1,35 @@
using System.ComponentModel;
using NSExt.Extensions;
using Spectre.Console.Rendering;
namespace Dot.Time;
public class TaskStatusColumn : ProgressColumn
{
public enum Statues : byte
{
[Description($"[gray]{nameof(Ready)}[/]")]
Ready
, [Description($"[yellow]{nameof(Connecting)}[/]")]
Connecting
, [Description($"[green]{nameof(Succeed)}[/]")]
Succeed
, [Description($"[red]{nameof(Failed)}[/]")]
Failed
}
/// <summary>
/// Gets or sets the alignment of the task description.
/// </summary>
public Justify Alignment { get; set; } = Justify.Right;
/// <inheritdoc />
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
var text = task.State.Get<Statues>(nameof(TaskStatusColumn));
return new Markup(text.Desc()).Overflow(Overflow.Ellipsis).Justify(Alignment);
}
}

View File

@ -27,6 +27,8 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
}
}
protected abstract Task Core();
protected static Task LoadingAnimate(int x, int y, out CancellationTokenSource cts)
{
@ -50,6 +52,12 @@ public abstract class ToolBase<TOption> : ITool where TOption : OptionBase
});
}
public abstract Task Run();
public virtual async Task Run()
{
await Core();
if (Opt.KeepSession) {
AnsiConsole.MarkupLine(Str.PressAnyKey);
AnsiConsole.Console.Input.ReadKey(true);
}
}
}