1 #Region "Microsoft.VisualBasic::ba953e8a35ef058e489ed579b7204485, Microsoft.VisualBasic.Core\ApplicationServices\Terminal\ProgressBar\ProgressBar.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     '     Structure ColorTheme
35     
36     '         Properties: BackgroundColor, IsEmpty, MessageDetailColor, ProgressBarColor, ProgressMsgColor
37     
38     '         Function: [Default], DefaultTheme
39     
40     '     Class ProgressBar
41     
42     '         Properties: ElapsedMilliseconds
43     
44     '         Constructor: (+3 OverloadsSub New
45     '         Sub: [Step], __resize, __tick, (+2 Overloads) Dispose, SetProgress
46     '              SetToEchoLine
47     
48     '     Class ProgressProvider
49     
50     '         Properties: Current, Target
51     
52     '         Constructor: (+1 OverloadsSub New
53     '         Function: [Step], (+2 Overloads) ETA, StepProgress, ToString
54     
55     
56     ' /********************************************************************************/
57
58 #End Region
59
60 Imports System.Drawing
61 Imports System.Runtime.CompilerServices
62 Imports Microsoft.VisualBasic.Language.Default
63 Imports Microsoft.VisualBasic.Serialization.JSON
64
65 Namespace Terminal.ProgressBar
66
67     ''' <summary>
68     ''' The <see cref="ConsoleColor"/> theme for the <see cref="ProgressBar"/>
69     ''' </summary>
70     Public Structure ColorTheme : Implements IsEmpty
71
72         ' [ERROR 2017/11/5 下午 0704:37] <Print>:System.Exception: Print 
73         '  ---> System.Reflection.TargetInvocationException: 调用的目标发生了异常。 
74         '  ---> System.Exception: [
75         '           "未能从程序集“Microsoft.VisualBasic.Architecture.Framework_v3.0_22.0.76.201__8da45dcd8060cc9a, Version=3.0.32.34335, Culture=neutral, PublicKeyToken=null”中加载类型“Microsoft.VisualBasic.Terminal.ColorTheme”。",
76         '           "未能从程序集“Microsoft.VisualBasic.Architecture.Framework_v3.0_22.0.76.201__8da45dcd8060cc9a, Version=3.0.32.34335, Culture=neutral, PublicKeyToken=null”中加载类型“Microsoft.VisualBasic.Terminal.ColorTheme”。"
77         '       ] 
78         '  ---> System.Reflection.ReflectionTypeLoadException: 无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性。
79
80         ' 在 System.Reflection.RuntimeModule.GetTypes(RuntimeModule Module)
81         ' 在 System.Reflection.Assembly.GetTypes()
82         ' 在 Microsoft.VisualBasic.CommandLine.Reflection.RunDllEntryPoint.GetTypes(Assembly assm)
83         ' --- 内部异常堆栈跟踪的结尾 ---
84         ' 在 Microsoft.VisualBasic.CommandLine.Reflection.RunDllEntryPoint.GetTypes(Assembly assm)
85         ' 在 Microsoft.VisualBasic.CommandLine.Reflection.RunDllEntryPoint.GetDllMethod(Assembly assembly, String entryPoint)
86         ' 在 SMRUCC.WebCloud.httpd.CLI.RunDll(CommandLine args)
87         ' --- 内部异常堆栈跟踪的结尾 ---
88         ' 在 System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
89         ' 在 System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
90         ' 在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
91         ' 在 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
92         ' 在 Microsoft.VisualBasic.CommandLine.Reflection.EntryPoints.APIEntryPoint.__directInvoke(Object[] callParameters, Object target, Boolean Throw)
93         ' --- 内部异常堆栈跟踪的结尾 ---
94
95         Public Property BackgroundColor As ConsoleColor
96         Public Property ProgressBarColor As ConsoleColor
97         Public Property ProgressMsgColor As ConsoleColor
98         Public Property MessageDetailColor As ConsoleColor
99
100         ''' <summary>
101         ''' Test if all of the property value is equals to ZERO(<see cref="ConsoleColor.Black"/>).
102         ''' </summary>
103         ''' <returns></returns>
104         Public ReadOnly Property IsEmpty As Boolean Implements IsEmpty.IsEmpty
105             <MethodImpl(MethodImplOptions.AggressiveInlining)>
106             Get
107                 Return BackgroundColor = 0 AndAlso
108                     ProgressBarColor = 0 AndAlso
109                     ProgressMsgColor = 0 AndAlso
110                     MessageDetailColor = 0
111             End Get
112         End Property
113
114         ''' <summary>
115         ''' Default value for optional parameter
116         ''' </summary>
117         ''' <returns></returns>
118         ''' <remarks>
119         ''' ###### 2017-11-5
120         ''' This property will cause bug in reflection.
121         ''' 
122         ''' ###### 2017-11-6
123         ''' Change from property to function to avoid bug caused application crashed
124         ''' </remarks>
125         Public Shared Function DefaultTheme() As DefaultValue(Of ColorTheme)
126             Return New DefaultValue(Of ColorTheme) With {
127                 .Value = [Default](),
128                 .assert = Function(t)
129                               Return DirectCast(t, ColorTheme).IsEmpty
130                           End Function
131             }
132         End Function
133         ''' <summary>
134         ''' The default color theme values
135         ''' </summary>
136         ''' <returns></returns>
137         Public Shared Function [Default]() As ColorTheme
138             Return New ColorTheme With {
139                 .BackgroundColor = ConsoleColor.Cyan,
140                 .MessageDetailColor = ConsoleColor.White,
141                 .ProgressBarColor = ConsoleColor.Yellow,
142                 .ProgressMsgColor = ConsoleColor.Green
143             }
144         End Function
145     End Structure
146
147     ''' <summary>
148     ''' Progress bar for the <see cref="Console"/> Screen.
149     ''' </summary>
150     ''' <remarks>
151     ''' http://www.cnblogs.com/masonlu/p/4668232.html
152     ''' </remarks>
153     Public Class ProgressBar : Inherits AbstractBar
154         Implements IDisposable
155
156         Dim colorBack As ConsoleColor = Console.BackgroundColor
157         Dim colorFore As ConsoleColor = Console.ForegroundColor
158
159         ''' <summary>
160         ''' The current progress percentage: [0, 100]
161         ''' </summary>
162         Dim current%
163         Dim y%
164         Dim theme As ColorTheme
165
166         Shared ReadOnly disabled As Boolean
167
168         Shared Sub New()
169             disabled = App.GetVariable("progress_bar").TextEquals(NameOf(disabled))
170         End Sub
171
172         ''' <summary>
173         ''' Create a console progress bar object with custom configuration.
174         ''' </summary>
175         ''' <param name="title">The title of the task which will takes long time for running.</param>
176         ''' <param name="Y">The row position number of the progress bar.</param>
177         ''' <param name="CLS">Clear the console screen?</param>
178         Sub New(title$, Y%, Optional CLS As Boolean = FalseOptional theme As ColorTheme = Nothing)
179             If CLS AndAlso App.IsConsoleApp AndAlso Not disabled Then
180                 Call Console.Clear()
181             End If
182
183             Call Console.WriteLine(title)
184
185             Me.theme = theme Or ColorTheme.DefaultTheme
186             Me.y = Y
187
188             If Not disabled Then
189                 AddHandler TerminalEvents.Resize, AddressOf __resize
190
191                 Try
192                     Call __resize(Nothing, Nothing)
193                 Catch ex As Exception
194
195                 End Try
196             End If
197         End Sub
198
199         ''' <summary>
200         ''' Create a console progress bar with default theme color <see cref="ColorTheme.DefaultTheme"/>
201         ''' (在当前位置之后设置进度条,这个构造函数不会清除整个终端屏幕)
202         ''' </summary>
203         ''' <param name="title$">The task title</param>
204         Sub New(title$)
205             Call Me.New(title, Y:=Console.CursorTop, CLS:=False)
206         End Sub
207
208         Private Sub __resize(size As Size, old As Size)
209             Console.ResetColor()
210             Console.SetCursorPosition(0, y)
211             Console.BackgroundColor = ConsoleColor.DarkCyan
212             For i = 0 To Console.WindowWidth - 3
213                 '(0,1) 第二行
214                 Console.Write(" ")
215             Next
216             '(0,1) 第二行
217             Console.WriteLine(" ")
218             Console.BackgroundColor = colorBack
219
220             Call SetToEchoLine()
221         End Sub
222
223         ' title
224         ' progress bar
225         ' details
226         '
227         ' echo
228
229         ''' <summary>
230         ''' 将终端的输出位置放置到详细信息的下一行
231         ''' </summary>
232         <MethodImpl(MethodImplOptions.AggressiveInlining)>
233         Public Sub SetToEchoLine()
234             If Not disabled Then
235                 Console.SetCursorPosition(0, y + 3)
236             End If
237         End Sub
238
239         Public Overrides Sub [Step]()
240             Call SetProgress(current)
241             current += 1
242         End Sub
243
244         Dim timer As Stopwatch = Stopwatch.StartNew
245
246         ''' <summary>
247         ''' 获取当前实例测量得出的总运行时间(以毫秒为单位)。
248         ''' </summary>
249         ''' <returns>
250         ''' 一个只读长整型,表示当前实例测量得出的总毫秒数。
251         ''' </returns>
252         Public ReadOnly Property ElapsedMilliseconds As Long
253             <MethodImpl(MethodImplOptions.AggressiveInlining)>
254             Get
255                 Return timer.ElapsedMilliseconds
256             End Get
257         End Property
258
259         ''' <summary>
260         ''' 更新进度条的当前状态信息
261         ''' </summary>
262         ''' <param name="p%"></param>
263         ''' <param name="details$"></param>
264         Private Sub __tick(p%, details$)
265             Console.BackgroundColor = ConsoleColor.Yellow
266             ' /运算返回完整的商,包括余数,SetCursorPosition会自动四舍五入
267             Dim cx As Integer = p * (Console.WindowWidth - 2) / 100
268
269             Console.SetCursorPosition(0, y)
270
271             If p < current Then
272                 Call __resize(Nothing, Nothing)
273             End If
274
275             For i As Integer = 0 To cx
276                 Console.Write(" ")
277             Next
278
279             Console.BackgroundColor = colorBack
280             Console.ForegroundColor = ConsoleColor.Green
281             Console.SetCursorPosition(0, y + 1)
282             Console.Write("{0}%", p)
283             Console.ForegroundColor = colorFore
284
285             If Not String.IsNullOrEmpty(details) Then
286                 Console.WriteLine(vbTab & details)
287             End If
288
289             Call SetToEchoLine()
290         End Sub
291
292         ''' <summary>
293         ''' <paramref name="percent"/>是进度条的百分比
294         ''' </summary>
295         ''' <param name="percent">Percentage, 假设是从p到current</param>
296         Public Sub SetProgress(percent%, Optional details$ = "")
297             current = percent
298
299             If Not disabled Then
300                 Call __tick(current, details)
301             End If
302         End Sub
303
304 #Region "IDisposable Support"
305         Private disposedValue As Boolean To detect redundant calls
306
307         ' IDisposable
308         Protected Overridable Sub Dispose(disposing As Boolean)
309             If Not disposedValue Then
310                 If disposing Then
311                     If Not disabled Then
312                         ' TODO: dispose managed state (managed objects).
313                         RemoveHandler TerminalEvents.Resize, AddressOf __resize
314                     End If
315
316                     Call timer.Stop()
317                 End If
318
319                 ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
320                 ' TODO: set large fields to null.
321             End If
322             disposedValue = True
323         End Sub
324
325         ' TODO: override Finalize() only if Dispose(disposing As Boolean) above has code to free unmanaged resources.
326         'Protected Overrides Sub Finalize()
327         '    ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
328         '    Dispose(False)
329         '    MyBase.Finalize()
330         'End Sub
331
332         ' This code added by Visual Basic to correctly implement the disposable pattern.
333         Public Sub Dispose() Implements IDisposable.Dispose
334             Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
335             Dispose(True)
336             ' TODO: uncomment the following line if Finalize() is overridden above.
337             ' GC.SuppressFinalize(Me)
338         End Sub
339 #End Region
340     End Class
341
342     Public Class ProgressProvider
343
344         ''' <summary>
345         ''' 整个工作的总的tick数
346         ''' </summary>
347         ''' <returns></returns>
348         Public ReadOnly Property Target As Integer
349         ''' <summary>
350         ''' 当前已经完成的tick数
351         ''' </summary>
352         ''' <returns></returns>
353         Public ReadOnly Property Current As Integer
354
355         ''' <summary>
356         ''' 生成进度条的百分比值
357         ''' </summary>
358         ''' <param name="total"></param>
359         Sub New(total As Integer)
360             Target = total
361         End Sub
362
363         Dim previous#
364         Dim previousTime&
365
366         Public Function ETA(elapsed&, Optional avg As Boolean = TrueAs TimeSpan
367             Dim out As TimeSpan
368
369             If avg Then
370                 out = ETA(0R, Current / Target, elapsed)
371             Else
372                 out = ETA(
373                     previous,
374                     Current / Target,
375                     elapsed - previousTime)
376
377                 previousTime = elapsed
378                 previous = Current / Target
379             End If
380
381             Return out
382         End Function
383
384         ''' <summary>
385         ''' 返回来的百分比小数,还需要乘以100才能得到进度
386         ''' </summary>
387         ''' <returns></returns>
388         Public Function [Step]() As Double
389             _Current += 1
390             Return Current / Target
391         End Function
392
393         ''' <summary>
394         ''' 百分比进度,不需要再乘以100了
395         ''' </summary>
396         ''' <returns></returns>
397         Public Function StepProgress() As Integer
398             Return CInt([Step]() * 100)
399         End Function
400
401         Public Overrides Function ToString() As String
402             Return Me.GetJson
403         End Function
404
405         ''' <summary>
406         ''' 
407         ''' </summary>
408         ''' <param name="previous#">百分比</param>
409         ''' <param name="cur#">百分比</param>
410         ''' <param name="Elapsed#">当前的这个百分比差所经历过的时间</param>
411         ''' <returns></returns>
412         Public Shared Function ETA(previous#, cur#, Elapsed&) As TimeSpan
413             Dim d# = cur - previous
414
415             If d# = 0R Then
416                 d = 0.000000001
417             End If
418
419             Dim lefts = (1 - cur) / d   ' lefts = 100% - currents
420             Dim time = lefts * Elapsed
421             Dim estimates = TimeSpan.FromMilliseconds(time)
422             Return estimates
423         End Function
424     End Class
425 End Namespace