mirror of
https://github.com/nsnail/dot.git
synced 2025-06-19 05:48:16 +08:00
<refactor> ui美化...
This commit is contained in:
2
LICENSE
2
LICENSE
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
17
safe-delete-unused-resx.ahk
Normal file
17
safe-delete-unused-resx.ahk
Normal 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
|
||||||
|
}
|
@ -5,7 +5,7 @@ public sealed class Main : ToolBase<Option>
|
|||||||
{
|
{
|
||||||
public Main(Option opt) : base(opt) { }
|
public Main(Option opt) : base(opt) { }
|
||||||
|
|
||||||
public override Task Run()
|
protected override Task Core()
|
||||||
{
|
{
|
||||||
Application.Run(new WinMain());
|
Application.Run(new WinMain());
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -45,6 +45,39 @@ public abstract class FilesTool<TOption> : ToolBase<TOption> where TOption : Dir
|
|||||||
return fileList;
|
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)
|
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);
|
_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));
|
|
||||||
}
|
|
||||||
}
|
}
|
132
src/Git/Main.cs
132
src/Git/Main.cs
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using NSExt.Extensions;
|
using NSExt.Extensions;
|
||||||
@ -6,13 +7,9 @@ namespace Dot.Git;
|
|||||||
|
|
||||||
public class Main : ToolBase<Option>
|
public class Main : ToolBase<Option>
|
||||||
{
|
{
|
||||||
private const int _POS_Y_MSG = 74; //git command rsp 显示的位置 y
|
private readonly Encoding _gitOutputEnc; //git command rsp 编码
|
||||||
private const int _POST_Y_LOADING = 70; //loading 动画显示的位置 y
|
private ConcurrentDictionary<string, StringBuilder> _repoRsp; //仓库信息容器
|
||||||
private const int _REP_PATH_LENGTH_LIMIT = 32; //仓库路径长度显示截断阈值
|
private ConcurrentDictionary<string, TaskStatusColumn.Statues> _repoStatus;
|
||||||
private (int x, int y) _cursorPosBackup; //光标位置备份
|
|
||||||
private readonly Encoding _gitOutputEnc; //git command rsp 编码
|
|
||||||
private List<string> _repoPathList; //仓库目录列表
|
|
||||||
|
|
||||||
|
|
||||||
public Main(Option opt) : base(opt)
|
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); // 行号
|
payload.Value.StartTask();
|
||||||
var tAnimate = LoadingAnimate(_POST_Y_LOADING, _cursorPosBackup.y + row, out var cts);
|
payload.Value.State.Status(TaskStatusColumn.Statues.Executing);
|
||||||
|
|
||||||
// 打印 git command rsp
|
// 打印 git command rsp
|
||||||
void ExecRspReceived(object sender, DataReceivedEventArgs e)
|
void ExecRspReceived(object sender, DataReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Data is null) return;
|
if (e.Data is null) return;
|
||||||
var msg = Encoding.UTF8.GetString(_gitOutputEnc.GetBytes(e.Data));
|
var msg = Encoding.UTF8.GetString(_gitOutputEnc.GetBytes(e.Data));
|
||||||
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, new string(' ', Console.WindowWidth - _POS_Y_MSG));
|
_repoRsp[payload.Key].Append(msg.EscapeMarkup());
|
||||||
ConcurrentWrite(_POS_Y_MSG, _cursorPosBackup.y + row, msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动git进程
|
// 启动git进程
|
||||||
{
|
{
|
||||||
var startInfo = new ProcessStartInfo {
|
var startInfo = new ProcessStartInfo {
|
||||||
CreateNoWindow = true
|
CreateNoWindow = true
|
||||||
, WorkingDirectory = dir
|
, WorkingDirectory = payload.Key
|
||||||
, FileName = "git"
|
, FileName = "git"
|
||||||
, Arguments = Opt.Args
|
, Arguments = Opt.Args
|
||||||
, UseShellExecute = false
|
, UseShellExecute = false
|
||||||
@ -54,58 +50,78 @@ public class Main : ToolBase<Option>
|
|||||||
p.BeginOutputReadLine();
|
p.BeginOutputReadLine();
|
||||||
p.BeginErrorReadLine();
|
p.BeginErrorReadLine();
|
||||||
await p.WaitForExitAsync();
|
await p.WaitForExitAsync();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cts.Cancel();
|
|
||||||
await tAnimate;
|
|
||||||
cts.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StashCurorPos()
|
|
||||||
{
|
|
||||||
_cursorPosBackup = Console.GetCursorPosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override async Task Run()
|
protected override async Task Core()
|
||||||
{
|
{
|
||||||
// 查找git仓库目录
|
var progressBar = new ProgressBarColumn { Width = 10 };
|
||||||
{
|
await AnsiConsole.Progress()
|
||||||
Console.Write(Str.FindGitReps, Opt.Path);
|
.Columns(progressBar //
|
||||||
StashCurorPos();
|
, 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
|
||||||
|
})
|
||||||
|
.Select(x => Directory.GetParent(x)!.FullName);
|
||||||
|
|
||||||
var tAnimate = LoadingAnimate(_cursorPosBackup.x, _cursorPosBackup.y, out var cts);
|
_repoRsp = new ConcurrentDictionary<string, StringBuilder>();
|
||||||
_repoPathList = Directory.GetDirectories(Opt.Path, ".git" //
|
_repoStatus = new ConcurrentDictionary<string, TaskStatusColumn.Statues>();
|
||||||
, new EnumerationOptions //
|
var tasks = new Dictionary<string, ProgressTask>();
|
||||||
{
|
foreach (var path in paths) {
|
||||||
MaxRecursionDepth = Opt.MaxRecursionDepth
|
_repoRsp.TryAdd(path, new StringBuilder());
|
||||||
, RecurseSubdirectories = true
|
_repoStatus.TryAdd(path, default);
|
||||||
, IgnoreInaccessible = true
|
var task = ctx.AddTask(new DirectoryInfo(path).Name, false).IsIndeterminate();
|
||||||
, AttributesToSkip = FileAttributes.ReparsePoint
|
tasks.Add(path, task);
|
||||||
})
|
}
|
||||||
.Select(x => Directory.GetParent(x)!.FullName)
|
|
||||||
.ToList();
|
taskFinder.IsIndeterminate(false);
|
||||||
cts.Cancel();
|
taskFinder.Increment(100);
|
||||||
await tAnimate;
|
taskFinder.State.Status(TaskStatusColumn.Statues.Succeed);
|
||||||
cts.Dispose();
|
|
||||||
|
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仓库目录
|
AnsiConsole.Write(table);
|
||||||
{
|
|
||||||
Console.WriteLine(Str.Ok);
|
|
||||||
StashCurorPos();
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
Console.WriteLine( //
|
|
||||||
string.Join(Environment.NewLine
|
|
||||||
, _repoPathList.Select(
|
|
||||||
x => $"{++i}: {new DirectoryInfo(x).Name.Sub(0, _REP_PATH_LENGTH_LIMIT)}"))
|
|
||||||
//
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 并行执行git命令
|
|
||||||
await Parallel.ForEachAsync(_repoPathList, DirHandle);
|
|
||||||
Console.SetCursorPosition(_cursorPosBackup.x, _cursorPosBackup.y + _repoPathList.Count);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
14
src/Git/ProgressTaskStateExtensions.cs
Normal file
14
src/Git/ProgressTaskStateExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
35
src/Git/TaskStatusColumn.cs
Normal file
35
src/Git/TaskStatusColumn.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ public sealed class Main : ToolBase<Option>
|
|||||||
public Main(Option opt) : base(opt) { }
|
public Main(Option opt) : base(opt) { }
|
||||||
|
|
||||||
|
|
||||||
public override Task Run()
|
protected override Task Core()
|
||||||
{
|
{
|
||||||
var guid = System.Guid.NewGuid().ToString();
|
var guid = System.Guid.NewGuid().ToString();
|
||||||
if (Opt.Upper) guid = guid.ToUpper();
|
if (Opt.Upper) guid = guid.ToUpper();
|
||||||
|
@ -8,7 +8,7 @@ public sealed class Main : ToolBase<Option>
|
|||||||
{
|
{
|
||||||
public Main(Option opt) : base(opt) { }
|
public Main(Option opt) : base(opt) { }
|
||||||
|
|
||||||
public override async Task Run()
|
protected override async Task Core()
|
||||||
{
|
{
|
||||||
foreach (var item in NetworkInterface.GetAllNetworkInterfaces()) {
|
foreach (var item in NetworkInterface.GetAllNetworkInterfaces()) {
|
||||||
if (item.NetworkInterfaceType != NetworkInterfaceType.Ethernet ||
|
if (item.NetworkInterfaceType != NetworkInterfaceType.Ethernet ||
|
||||||
|
@ -53,7 +53,7 @@ public class Main : ToolBase<Option>
|
|||||||
return text.Replace("\\\"", "\"");
|
return text.Replace("\\\"", "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task Run()
|
protected override async Task Core()
|
||||||
{
|
{
|
||||||
string result = null;
|
string result = null;
|
||||||
if (Opt.Compress)
|
if (Opt.Compress)
|
||||||
|
@ -1,190 +1,212 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<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"
|
||||||
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
|
id="root" xmlns="">
|
||||||
</xsd:schema>
|
<xsd:element name="root" msdata:IsDataSet="true"></xsd:element>
|
||||||
<resheader name="resmimetype">
|
</xsd:schema>
|
||||||
<value>text/microsoft-resx</value>
|
<resheader name="resmimetype">
|
||||||
</resheader>
|
<value>text/microsoft-resx</value>
|
||||||
<resheader name="version">
|
</resheader>
|
||||||
<value>1.3</value>
|
<resheader name="version">
|
||||||
</resheader>
|
<value>1.3</value>
|
||||||
<resheader name="reader">
|
</resheader>
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||||
PublicKeyToken=b77a5c561934e089
|
PublicKeyToken=b77a5c561934e089
|
||||||
</value>
|
</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
|
||||||
PublicKeyToken=b77a5c561934e089
|
PublicKeyToken=b77a5c561934e089
|
||||||
</value>
|
</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="InputTextIsEmpty" xml:space="preserve">
|
<data name="InputTextIsEmpty" xml:space="preserve">
|
||||||
<value>The input text is empty</value>
|
<value>The input text is empty</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SearchingFile" xml:space="preserve">
|
<data name="SearchingFile" xml:space="preserve">
|
||||||
<value>Find files...</value>
|
<value>Find files...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PathNotFound" xml:space="preserve">
|
<data name="PathNotFound" xml:space="preserve">
|
||||||
<value>The specified path "{0}" does not exist</value>
|
<value>The specified path "{0}" does not exist</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SearchingFileOK" xml:space="preserve">
|
<data name="SearchingFileOK" xml:space="preserve">
|
||||||
<value>{0} files</value>
|
<value>{0} files</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ShowMessageTemp" xml:space="preserve">
|
<data name="ShowMessageTemp" xml:space="preserve">
|
||||||
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
|
<value>Read: {0}/{1}, processed: {2}, skipped: {3}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Copied" xml:space="preserve">
|
<data name="Copied" xml:space="preserve">
|
||||||
<value>{0}(copied to clipboard)</value>
|
<value>{0}(copied to clipboard)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSearchPattern" xml:space="preserve">
|
<data name="FileSearchPattern" xml:space="preserve">
|
||||||
<value>File wildcards</value>
|
<value>File wildcards</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FolderPath" xml:space="preserve">
|
<data name="FolderPath" xml:space="preserve">
|
||||||
<value>Directory path to be processed</value>
|
<value>Directory path to be processed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ConvertEndOfLineToLF" xml:space="preserve">
|
<data name="ConvertEndOfLineToLF" xml:space="preserve">
|
||||||
<value>Convert newline characters to LF</value>
|
<value>Convert newline characters to LF</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GuidTool" xml:space="preserve">
|
<data name="GuidTool" xml:space="preserve">
|
||||||
<value>GUID tool</value>
|
<value>GUID tool</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UseUppercase" xml:space="preserve">
|
<data name="UseUppercase" xml:space="preserve">
|
||||||
<value>Use uppercase output</value>
|
<value>Use uppercase output</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RandomPasswordGenerator" xml:space="preserve">
|
<data name="RandomPasswordGenerator" xml:space="preserve">
|
||||||
<value>Random password generator</value>
|
<value>Random password generator</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PwdLength" xml:space="preserve">
|
<data name="PwdLength" xml:space="preserve">
|
||||||
<value>Password length</value>
|
<value>Password length</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PwdGenerateTypes" xml:space="preserve">
|
<data name="PwdGenerateTypes" xml:space="preserve">
|
||||||
<value>BitSet 1:[0-9],2:[a-z],4:[A-Z],8:[ascii.0x21-0x2F]</value>
|
<value>BitSet 1:[0-9],2:[a-z],4:[A-Z],8:[ascii.0x21-0x2F]</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoveTrailingWhiteSpaces" xml:space="preserve">
|
<data name="RemoveTrailingWhiteSpaces" xml:space="preserve">
|
||||||
<value>Remove line breaks and spaces at the end of the file</value>
|
<value>Remove line breaks and spaces at the end of the file</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TrimUtf8Bom" xml:space="preserve">
|
<data name="TrimUtf8Bom" xml:space="preserve">
|
||||||
<value>Remove the uf8 bom of the file</value>
|
<value>Remove the uf8 bom of the file</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TextTobeProcessed" xml:space="preserve">
|
<data name="TextTobeProcessed" xml:space="preserve">
|
||||||
<value>Text to be processed (clipboard value is taken by default)</value>
|
<value>Text to be processed (clipboard value is taken by default)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PressAnyKey" xml:space="preserve">
|
<data name="PressAnyKey" xml:space="preserve">
|
||||||
<value>Press any key to continue...</value>
|
<value>Press any key to continue...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NoFileToBeProcessed" xml:space="preserve">
|
<data name="NoFileToBeProcessed" xml:space="preserve">
|
||||||
<value>No documents to be processed</value>
|
<value>No documents to be processed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TimeoutMillSecs" xml:space="preserve">
|
<data name="TimeoutMillSecs" xml:space="preserve">
|
||||||
<value>Timeout for connecting to the NTP server (milliseconds)</value>
|
<value>Timeout for connecting to the NTP server (milliseconds)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SyncToLocalTime" xml:space="preserve">
|
<data name="SyncToLocalTime" xml:space="preserve">
|
||||||
<value>Synchronize local time</value>
|
<value>Synchronize local time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NtpReceiveDone" xml:space="preserve">
|
<data name="NtpReceiveDone" xml:space="preserve">
|
||||||
<value>Success {0}/{1}, the average value of the clock offset of the machine:{2}ms</value>
|
<value>Success {0}/{1}, the average value of the clock offset of the machine:{2}ms</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NtpServerCount" xml:space="preserve">
|
<data name="NtpServerCount" xml:space="preserve">
|
||||||
<value>{0}/{1} NTP servers</value>
|
<value>{0}/{1} NTP servers</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NtpCalling" xml:space="preserve">
|
<data name="NtpCalling" xml:space="preserve">
|
||||||
<value>{0} In communication...</value>
|
<value>{0} In communication...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LocalTimeOffset" xml:space="preserve">
|
<data name="LocalTimeOffset" xml:space="preserve">
|
||||||
<value>{0}, local clock offset: {1} ms</value>
|
<value>{0}, local clock offset: {1} ms</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LocalTimeSyncDone" xml:space="preserve">
|
<data name="LocalTimeSyncDone" xml:space="preserve">
|
||||||
<value>Local time has been synchronized</value>
|
<value>Local time has been synchronized</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Server" xml:space="preserve">
|
<data name="Server" xml:space="preserve">
|
||||||
<value>Server</value>
|
<value>Server</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Status" xml:space="preserve">
|
<data name="Status" xml:space="preserve">
|
||||||
<value>Status</value>
|
<value>Status</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LocalClockOffset" xml:space="preserve">
|
<data name="LocalClockOffset" xml:space="preserve">
|
||||||
<value>Local clock offset</value>
|
<value>Local clock offset</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TimeTool" xml:space="preserve">
|
<data name="TimeTool" xml:space="preserve">
|
||||||
<value>Time synchronization tool</value>
|
<value>Time synchronization tool</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TextTool" xml:space="preserve">
|
<data name="TextTool" xml:space="preserve">
|
||||||
<value>Text encoding tool</value>
|
<value>Text encoding tool</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ScreenPixelTool" xml:space="preserve">
|
<data name="ScreenPixelTool" xml:space="preserve">
|
||||||
<value>Screen coordinate color selection tool</value>
|
<value>Screen coordinate color selection tool</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ClickCopyColor" xml:space="preserve">
|
<data name="ClickCopyColor" xml:space="preserve">
|
||||||
<value>Click the left mouse button to copy the colors and coordinates to the clipboard</value>
|
<value>Click the left mouse button to copy the colors and coordinates to the clipboard</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PublicIP" xml:space="preserve">
|
<data name="PublicIP" xml:space="preserve">
|
||||||
<value>Public network ip: </value>
|
<value>Public network ip: </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ServerTime" xml:space="preserve">
|
<data name="ServerTime" xml:space="preserve">
|
||||||
<value>Synchronize local time</value>
|
<value>Synchronize local time</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Ip" xml:space="preserve">
|
<data name="Ip" xml:space="preserve">
|
||||||
<value>IP tools</value>
|
<value>IP tools</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="KeepSession" xml:space="preserve">
|
<data name="KeepSession" xml:space="preserve">
|
||||||
<value>Keep the session after executing the command</value>
|
<value>Keep the session after executing the command</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NtpServerTime" xml:space="preserve">
|
<data name="NtpClock" xml:space="preserve">
|
||||||
<value>NTP server standard clock: {0}</value>
|
<value>NTP server standard clock: {0}</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GitTool" xml:space="preserve">
|
<data name="GitTool" xml:space="preserve">
|
||||||
<value>Git batch operation tool</value>
|
<value>Git batch operation tool</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GitArgs" xml:space="preserve">
|
<data name="GitArgs" xml:space="preserve">
|
||||||
<value>Parameters passed to Git</value>
|
<value>Parameters passed to Git</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Ok" xml:space="preserve">
|
<data name="Ok" xml:space="preserve">
|
||||||
<value>OK</value>
|
<value>OK</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FindGitReps" xml:space="preserve">
|
<data name="FindGitReps" xml:space="preserve">
|
||||||
<value>Find all git repository directories under "{0}"...</value>
|
<value>Find all git repository directories under "{0}"...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GitOutputEncoding" xml:space="preserve">
|
<data name="GitOutputEncoding" xml:space="preserve">
|
||||||
<value>Git output encoding</value>
|
<value>Git output encoding</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MaxRecursionDepth" xml:space="preserve">
|
<data name="MaxRecursionDepth" xml:space="preserve">
|
||||||
<value>Directory search depth</value>
|
<value>Directory search depth</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="InvalidJsonString" xml:space="preserve">
|
<data name="InvalidJsonString" xml:space="preserve">
|
||||||
<value>Clipboard does not contain correct Json string</value>
|
<value>Clipboard does not contain correct Json string</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Json" xml:space="preserve">
|
<data name="Json" xml:space="preserve">
|
||||||
<value>JsonTools</value>
|
<value>JsonTools</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="CompressJson" xml:space="preserve">
|
<data name="CompressJson" xml:space="preserve">
|
||||||
<value>Compress Json text</value>
|
<value>Compress Json text</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FormatJson" xml:space="preserve">
|
<data name="FormatJson" xml:space="preserve">
|
||||||
<value>Format JSON text</value>
|
<value>Format JSON text</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="GeneratorClass" xml:space="preserve">
|
<data name="GeneratorClass" xml:space="preserve">
|
||||||
<value>generate entity classes</value>
|
<value>generate entity classes</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="JsonToString" xml:space="preserve">
|
<data name="JsonToString" xml:space="preserve">
|
||||||
<value>Json text escaped into a string</value>
|
<value>Json text escaped into a string</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="WriteMode" xml:space="preserve">
|
<data name="WriteMode" xml:space="preserve">
|
||||||
<value>Enable write mode</value>
|
<value>Enable write mode</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExerciseMode" xml:space="preserve">
|
<data name="ExerciseMode" xml:space="preserve">
|
||||||
<value>Read-only mode, the file will not be modified in real time!</value>
|
<value>Read-only mode, the file will not be modified in real time!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Read" xml:space="preserve">
|
<data name="Read" xml:space="preserve">
|
||||||
<value>read</value>
|
<value>read</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Write" xml:space="preserve">
|
<data name="Write" xml:space="preserve">
|
||||||
<value>write</value>
|
<value>write</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Break" xml:space="preserve">
|
<data name="Break" xml:space="preserve">
|
||||||
<value>skip</value>
|
<value>skip</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="WriteFileStats" xml:space="preserve">
|
<data name="WriteFileStats" xml:space="preserve">
|
||||||
<value>Write statistics</value>
|
<value>Write statistics</value>
|
||||||
</data>
|
</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>
|
</root>
|
@ -82,6 +82,9 @@
|
|||||||
<data name="SyncToLocalTime" xml:space="preserve">
|
<data name="SyncToLocalTime" xml:space="preserve">
|
||||||
<value>同步本机时间</value>
|
<value>同步本机时间</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="LocalClock" xml:space="preserve">
|
||||||
|
<value>本机时钟</value>
|
||||||
|
</data>
|
||||||
<data name="ServerTime" xml:space="preserve">
|
<data name="ServerTime" xml:space="preserve">
|
||||||
<value>同步本机时间</value>
|
<value>同步本机时间</value>
|
||||||
</data>
|
</data>
|
||||||
@ -161,8 +164,8 @@
|
|||||||
<data name="LocalTimeOffset" xml:space="preserve">
|
<data name="LocalTimeOffset" xml:space="preserve">
|
||||||
<value>{0}, 本机时钟偏移: {1} ms</value>
|
<value>{0}, 本机时钟偏移: {1} ms</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NtpServerTime" xml:space="preserve">
|
<data name="NtpClock" xml:space="preserve">
|
||||||
<value>NTP 服务器标准时钟: {0}</value>
|
<value>NTP 标准时钟</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LocalTimeSyncDone" xml:space="preserve">
|
<data name="LocalTimeSyncDone" xml:space="preserve">
|
||||||
<value>本机时间已同步</value>
|
<value>本机时间已同步</value>
|
||||||
@ -186,7 +189,7 @@
|
|||||||
<value>OK</value>
|
<value>OK</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FindGitReps" xml:space="preserve">
|
<data name="FindGitReps" xml:space="preserve">
|
||||||
<value>查找 "{0}" 下所有git仓库目录... </value>
|
<value>查找 "{0}" 下所有git仓库目录 </value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ExerciseMode" xml:space="preserve">
|
<data name="ExerciseMode" xml:space="preserve">
|
||||||
<value>只读模式, 不会真实修改文件!</value>
|
<value>只读模式, 不会真实修改文件!</value>
|
||||||
@ -205,4 +208,16 @@
|
|||||||
<data name="WriteFileStats" xml:space="preserve">
|
<data name="WriteFileStats" xml:space="preserve">
|
||||||
<value>写入统计</value>
|
<value>写入统计</value>
|
||||||
</data>
|
</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>
|
</root>
|
@ -14,13 +14,8 @@ Type[] LoadVerbs()
|
|||||||
async Task Run(object args)
|
async Task Run(object args)
|
||||||
{
|
{
|
||||||
if (args is not OptionBase option) return;
|
if (args is not OptionBase option) return;
|
||||||
|
|
||||||
var tool = ToolsFactory.Create(option);
|
var tool = ToolsFactory.Create(option);
|
||||||
await tool.Run();
|
await tool.Run();
|
||||||
if (option!.KeepSession) {
|
|
||||||
AnsiConsole.MarkupLine(Str.PressAnyKey);
|
|
||||||
AnsiConsole.Console.Input.ReadKey(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public sealed class Main : ToolBase<Option>
|
|||||||
|
|
||||||
public Main(Option opt) : base(opt) { }
|
public Main(Option opt) : base(opt) { }
|
||||||
|
|
||||||
public override Task Run()
|
protected override Task Core()
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
var pSource = stackalloc char[_charTable.Sum(x => x.Length)];
|
var pSource = stackalloc char[_charTable.Sum(x => x.Length)];
|
||||||
|
@ -99,7 +99,7 @@ html-decode: {o.HtmlDecode}
|
|||||||
Console.WriteLine(outputTemp);
|
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()) Opt.Text = await ClipboardService.GetTextAsync();
|
||||||
if (Opt.Text.NullOrEmpty()) throw new ArgumentException(Str.InputTextIsEmpty);
|
if (Opt.Text.NullOrEmpty()) throw new ArgumentException(Str.InputTextIsEmpty);
|
||||||
|
230
src/Time/Main.cs
230
src/Time/Main.cs
@ -1,98 +1,37 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
|
||||||
namespace Dot.Time;
|
namespace Dot.Time;
|
||||||
|
|
||||||
public sealed class Main : ToolBase<Option>
|
public sealed class Main : ToolBase<Option>
|
||||||
{
|
{
|
||||||
private record Server
|
private const int _MAX_DEGREE_OF_PARALLELISM = 10;
|
||||||
{
|
private const int _NTP_PORT = 123;
|
||||||
public int ConsoleRowIndex;
|
|
||||||
public TimeSpan Offset;
|
|
||||||
public ServerStatues Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ServerStatues : byte
|
private readonly string[] _ntpServers = {
|
||||||
{
|
"ntp.ntsc.ac.cn", "cn.ntp.org.cn", "edu.ntp.org.cn", "cn.pool.ntp.org"
|
||||||
Ready
|
, "time.pool.aliyun.com", "time1.aliyun.com", "time2.aliyun.com"
|
||||||
, Connecting
|
, "time3.aliyun.com", "time4.aliyun.com", "time5.aliyun.com"
|
||||||
, Succeed
|
, "time6.aliyun.com", "time7.aliyun.com", "time1.cloud.tencent.com"
|
||||||
, Failed
|
, "time2.cloud.tencent.com", "time3.cloud.tencent.com"
|
||||||
}
|
, "time4.cloud.tencent.com", "time5.cloud.tencent.com", "ntp.sjtu.edu.cn"
|
||||||
|
, "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"
|
||||||
|
};
|
||||||
|
|
||||||
|
private double _offsetAvg;
|
||||||
|
|
||||||
|
|
||||||
private const int _MAX_DEGREE_OF_PARALLELISM = 10;
|
private int _successCnt;
|
||||||
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 = {
|
|
||||||
"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"
|
|
||||||
, "time6.aliyun.com", "time7.aliyun.com", "time1.cloud.tencent.com"
|
|
||||||
, "time2.cloud.tencent.com", "time3.cloud.tencent.com"
|
|
||||||
, "time4.cloud.tencent.com", "time5.cloud.tencent.com", "ntp.sjtu.edu.cn"
|
|
||||||
, "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"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Dictionary<string, Server> _srvStatus;
|
|
||||||
private int _successCnt;
|
|
||||||
|
|
||||||
|
|
||||||
public Main(Option opt) : base(opt)
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private TimeSpan GetNtpOffset(string server)
|
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();
|
payload.Value.StartTask();
|
||||||
Console.CursorVisible = false;
|
payload.Value.State.Status(TaskStatusColumn.Statues.Connecting);
|
||||||
var row = //
|
var offset = GetNtpOffset(payload.Key);
|
||||||
_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);
|
|
||||||
|
|
||||||
if (offset == TimeSpan.Zero) {
|
if (offset == TimeSpan.Zero) {
|
||||||
ChangeStatus(server, ServerStatues.Failed);
|
payload.Value.State.Status(TaskStatusColumn.Statues.Failed);
|
||||||
|
payload.Value.IsIndeterminate(false).Increment(0);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
payload.Value.State.Status(TaskStatusColumn.Statues.Succeed);
|
||||||
|
payload.Value.State.Result(offset);
|
||||||
Interlocked.Increment(ref _successCnt);
|
Interlocked.Increment(ref _successCnt);
|
||||||
ChangeStatus(server, ServerStatues.Succeed, offset);
|
payload.Value.IsIndeterminate(false).Increment(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
@ -179,46 +106,67 @@ public sealed class Main : ToolBase<Option>
|
|||||||
Win32.SetLocalTime(timeToSet);
|
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()
|
public override async Task Run()
|
||||||
{
|
{
|
||||||
PrintTemplate();
|
await Core();
|
||||||
var tLoading = DrawLoading();
|
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"));
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await Parallel.ForEachAsync(_srvStatus
|
await AnsiConsole.Console.Input.ReadKeyAsync(true, cts.Token);
|
||||||
, new ParallelOptions { MaxDegreeOfParallelism = _MAX_DEGREE_OF_PARALLELISM }
|
cts.Cancel();
|
||||||
, (server, _) => ServerHandle(server));
|
await task;
|
||||||
|
|
||||||
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();
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
// ReSharper disable once FunctionNeverReturns
|
|
||||||
});
|
|
||||||
waitObj.WaitOne();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetSysteTime(DateTime.Now - avgOffset);
|
|
||||||
Console.WriteLine(Str.LocalTimeSyncDone);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
24
src/Time/ProgressTaskStateExtensions.cs
Normal file
24
src/Time/ProgressTaskStateExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
18
src/Time/TaskResultColumn.cs
Normal file
18
src/Time/TaskResultColumn.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
35
src/Time/TaskStatusColumn.cs
Normal file
35
src/Time/TaskStatusColumn.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
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 virtual async Task Run()
|
||||||
public abstract Task Run();
|
{
|
||||||
|
await Core();
|
||||||
|
if (Opt.KeepSession) {
|
||||||
|
AnsiConsole.MarkupLine(Str.PressAnyKey);
|
||||||
|
AnsiConsole.Console.Input.ReadKey(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user