diff --git a/.gitignore b/.gitignore index 1998c29..3cd5480 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -.cache \ No newline at end of file +.cache +*~ +*#*# +.appdata diff --git a/bin/progress.cmd b/bin/progress.cmd new file mode 100644 index 0000000..0126e86 --- /dev/null +++ b/bin/progress.cmd @@ -0,0 +1,2 @@ +@echo off +powershell -File %~dp0\progress.ps1 -- "%*" \ No newline at end of file diff --git a/bin/progress.cs b/bin/progress.cs new file mode 100644 index 0000000..09c38f1 --- /dev/null +++ b/bin/progress.cs @@ -0,0 +1,239 @@ +using System.Diagnostics; +using System.Windows.Forms; +using System.Drawing; +using System.Runtime.InteropServices; +using System; +using System.ComponentModel; +using System.Threading; + +public class Progress { + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2); + private const UInt32 SWP_NOSIZE = 0x0001; + private const UInt32 SWP_NOMOVE = 0x0002; + private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + private string line; + public Progress(string line) { + this.line = line; + } + + public void Run() { + Form f = new Form(); + f.Width = 800; + f.Height = 35; + f.Text = line; + f.StartPosition = FormStartPosition.CenterParent; + f.FormBorderStyle = FormBorderStyle.None; + f.Load += ((o, e) => f.TopMost = true); + TextBox t = new TextBox(); + t.Dock = DockStyle.Fill; + t.Multiline = true; + t.ReadOnly = true; + t.AcceptsReturn = true; + t.AcceptsTab = true; + t.Font = new Font("Consolas", 14); + f.Controls.Add(t); + SetWindowPos(f.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); + + Process proc = new Process(); + proc.StartInfo.UseShellExecute = false; + proc.StartInfo.FileName = "cmd"; + proc.StartInfo.Arguments = "/c " + line; + proc.StartInfo.CreateNoWindow = false; + proc.StartInfo.RedirectStandardOutput = true; + proc.StartInfo.RedirectStandardError = true; + proc.EnableRaisingEvents = true; + proc.OutputDataReceived += ((sender, args) => + f.BeginInvoke(new MethodInvoker( () => {t.AppendText("\r\n" + args.Data);})) + ); + + proc.ErrorDataReceived += ((sender, args) => + f.BeginInvoke(new MethodInvoker( () => {t.AppendText("\r\n" + args.Data);})) + ); + + proc.Exited += ((sender, args) => { + onexit(proc, f); + }); + try { + proc.Start(); + } catch (Exception) { + onexit(proc, f); + } + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + ChildProcessTracker.AddProcess(proc); + f.ShowDialog(); + + try { + if (!proc.HasExited) + { + proc.Kill(); + } + } catch (Exception){} + } + + void onexit(Process proc, Form f) { + SetWindowPos(f.Handle, HWND_NOTOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); + if (proc.ExitCode != 0) { + if (MessageBox.Show( + String.Format("上一条命令失败了, 是否重试: \n{0}", line), + "是否重试?", + MessageBoxButtons.RetryCancel, + MessageBoxIcon.Question) == DialogResult.Retry) { + new Thread(new ThreadStart(() => { + Thread.Sleep(500); + f.DialogResult = DialogResult.OK; + })).Start(); + Run(); + } else { + f.DialogResult = DialogResult.OK; + } + } else { + f.DialogResult = DialogResult.OK; + } + + } +} + + +//// http://stackoverflow.com/questions/3342941/kill-child-process-when-parent-process-is-killed + +/// +/// Allows processes to be automatically killed if this parent process unexpectedly quits. +/// This feature requires Windows 8 or greater. On Windows 7, nothing is done. +/// References: +/// http://stackoverflow.com/a/4657392/386091 +/// http://stackoverflow.com/a/9164742/386091 +public static class ChildProcessTracker +{ + /// + /// Add the process to be tracked. If our current process is killed, the child processes + /// that we are tracking will be automatically killed, too. If the child process terminates + /// first, that's fine, too. + /// + public static void AddProcess(Process process) + { + if (s_jobHandle != IntPtr.Zero) + { + bool success = AssignProcessToJobObject(s_jobHandle, process.Handle); + if (!success) + throw new Win32Exception(); + } + } + + static ChildProcessTracker() + { + // This feature requires Windows 8 or later. To support Windows 7 requires + // registry settings to be added if you are using Visual Studio plus an + // app.manifest change. + // http://stackoverflow.com/a/4232259/386091 + // http://stackoverflow.com/a/9507862/386091 + if (Environment.OSVersion.Version < new Version(6, 2)) + return; + + // The job name is optional (and can be null) but it helps with diagnostics. + // If it's not null, it has to be unique. Use SysInternals' Handle command-line + // utility: handle -a ChildProcessTracker + string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; + s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); + + var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION(); + + // This is the key flag. When our process is killed, Windows will automatically + // close the job handle, and when that happens, we want the child processes to + // be killed, too. + info.LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); + extendedInfo.BasicLimitInformation = info; + + int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); + try + { + Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); + + if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, + extendedInfoPtr, (uint)length)) + { + throw new Win32Exception(); + } + } + finally + { + Marshal.FreeHGlobal(extendedInfoPtr); + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); + + [DllImport("kernel32.dll")] + static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, + IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); + + // Windows will automatically close any open job handles when our process terminates. + // This can be verified by using SysInternals' Handle utility. When the job handle + // is closed, the child processes will be killed. + private static readonly IntPtr s_jobHandle; +} + +public enum JobObjectInfoType +{ + AssociateCompletionPortInformation = 7, + BasicLimitInformation = 2, + BasicUIRestrictions = 4, + EndOfJobTimeInformation = 6, + ExtendedLimitInformation = 9, + SecurityLimitInformation = 5, + GroupInformation = 11 +} + +[StructLayout(LayoutKind.Sequential)] +public struct JOBOBJECT_BASIC_LIMIT_INFORMATION +{ + public Int64 PerProcessUserTimeLimit; + public Int64 PerJobUserTimeLimit; + public JOBOBJECTLIMIT LimitFlags; + public UIntPtr MinimumWorkingSetSize; + public UIntPtr MaximumWorkingSetSize; + public UInt32 ActiveProcessLimit; + public Int64 Affinity; + public UInt32 PriorityClass; + public UInt32 SchedulingClass; +} + +[Flags] +public enum JOBOBJECTLIMIT : uint +{ + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 +} + +[StructLayout(LayoutKind.Sequential)] +public struct IO_COUNTERS +{ + public UInt64 ReadOperationCount; + public UInt64 WriteOperationCount; + public UInt64 OtherOperationCount; + public UInt64 ReadTransferCount; + public UInt64 WriteTransferCount; + public UInt64 OtherTransferCount; +} + +[StructLayout(LayoutKind.Sequential)] +public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION +{ + public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + public IO_COUNTERS IoInfo; + public UIntPtr ProcessMemoryLimit; + public UIntPtr JobMemoryLimit; + public UIntPtr PeakProcessMemoryUsed; + public UIntPtr PeakJobMemoryUsed; +} \ No newline at end of file diff --git a/bin/progress.ps1 b/bin/progress.ps1 new file mode 100644 index 0000000..a47bb8a --- /dev/null +++ b/bin/progress.ps1 @@ -0,0 +1,9 @@ +$cmdline = [System.Environment]::CommandLine +$index = $cmdline.IndexOf("--") +echo $cmdline +$cmdline = $cmdline.SubString($index + 2).Trim() +$dir = Split-Path -Parent $MyInvocation.MyCommand.Definition +$env:path="$dir;$env:path" +Add-Type -TypeDefinition (Get-Content $dir\progress.cs -raw) -ReferencedAssemblies System.Windows.Forms,System.Drawing,System.Runtime +$wnd = New-Object Progress($cmdline) +$wnd.Run() \ No newline at end of file diff --git a/start.cmd b/start.cmd index dcf33bd..f5b9784 100644 --- a/start.cmd +++ b/start.cmd @@ -1,3 +1,2 @@ @echo off -call %~dp0\env.cmd -cmd /k \ No newline at end of file +cmd /k %~dp0\env.cmd