@@ -22,13 +22,14 @@ import MLXVLM
2222// ── CLI ──────────────────────────────────────────────────────────────────────
2323
2424final class ProgressTracker {
25- var lastUpdate : TimeInterval = 0
26- var lastBytes : Int64 = 0
27- var speedStr = " 0.0 MB/s "
2825 var isDone = false
2926 var spinnerFrames = [ " ⠋ " , " ⠙ " , " ⠹ " , " ⠸ " , " ⠼ " , " ⠴ " , " ⠦ " , " ⠧ " , " ⠇ " , " ⠏ " ]
3027 var frameIndex = 0
3128 let modelId : String
29+ private var trackingTask : Task < Void , Never > ?
30+ private var lastUpdate : TimeInterval = 0
31+ private var lastBytes : Int64 = 0
32+ private var speedStr = " 0.0 MB/s "
3233
3334 init ( modelId: String ) {
3435 self . modelId = modelId
@@ -44,7 +45,6 @@ final class ProgressTracker {
4445 var total : Int64 = 0
4546 if let enumerator = FileManager . default. enumerator ( at: dir, includingPropertiesForKeys: [ . fileSizeKey] ) {
4647 for case let file as URL in enumerator {
47- // Quick check to skip symlinks from inflating size
4848 if let attr = try ? file. resourceValues ( forKeys: [ . fileSizeKey, . isSymbolicLinkKey] ) ,
4949 let size = attr. fileSize,
5050 attr. isSymbolicLink != true {
@@ -59,56 +59,74 @@ final class ProgressTracker {
5959 }
6060
6161 func printProgress( _ progress: Progress ) {
62- if isDone { return }
63- let now = Date ( ) . timeIntervalSince1970
64- let fraction = progress. fractionCompleted
65-
66- if lastUpdate == 0 {
67- lastUpdate = now
62+ if trackingTask == nil {
63+ lastUpdate = Date ( ) . timeIntervalSince1970
6864 lastBytes = getDownloadedBytes ( )
69- }
70- let interval = now - lastUpdate
71-
72- if interval > 0.5 {
73- frameIndex = ( frameIndex + 1 ) % spinnerFrames. count
7465
75- let currentBytes = getDownloadedBytes ( )
76- let diff = Double ( currentBytes - lastBytes)
77- if diff >= 0 {
78- let speedMBps = ( diff / interval) / 1_048_576.0
79- speedStr = String ( format: " %.1f MB/s " , speedMBps)
66+ trackingTask = Task {
67+ while !self . isDone && !Task. isCancelled {
68+ let now = Date ( ) . timeIntervalSince1970
69+ let fraction = progress. fractionCompleted
70+ let pct = Int ( fraction * 100 )
71+
72+ let interval = now - self . lastUpdate
73+ if interval >= 0.25 {
74+ self . frameIndex = ( self . frameIndex + 1 ) % self . spinnerFrames. count
75+
76+ let currentBytes = self . getDownloadedBytes ( )
77+ let diff = Double ( currentBytes - self . lastBytes)
78+ if diff >= 0 {
79+ let speedMBps = ( diff / interval) / 1_048_576.0
80+ self . speedStr = String ( format: " %.1f MB/s " , speedMBps)
81+ } else {
82+ // File moved/cleaned up cache, omit negative speed
83+ }
84+
85+ self . lastBytes = currentBytes
86+ self . lastUpdate = now
87+ }
88+
89+ var completedMB = String ( format: " %.1f " , Double ( self . lastBytes) / 1_048_576 )
90+ var totalMB = " ??? "
91+ if fraction > 0.001 {
92+ let extrapolated = ( Double ( self . lastBytes) / fraction) / 1_048_576.0
93+ totalMB = String ( format: " %.1f " , extrapolated)
94+ } else if fraction == 0.0 {
95+ completedMB = " 0.0 "
96+ }
97+
98+ let barLength = 20
99+ let completedBars = min ( barLength, Int ( fraction * Double( barLength) ) )
100+ let emptyBars = max ( 0 , barLength - completedBars)
101+
102+ var bars = " "
103+ if completedBars > 0 {
104+ bars += String ( repeating: " = " , count: completedBars - 1 ) + " > "
105+ }
106+ bars += String ( repeating: " " , count: emptyBars)
107+
108+ let pctStr = String ( format: " %3d%% " , pct)
109+ let spinner = self . spinnerFrames [ self . frameIndex]
110+ let speedText = " | Speed: \( self . speedStr) "
111+
112+ let msg = String ( format: " \r [mlx-server] Download: [%@] %@ %@ (%@ MB / %@ MB) %@ " , bars, pctStr, spinner, completedMB, totalMB, speedText)
113+
114+ print ( msg. padding ( toLength: 100 , withPad: " " , startingAt: 0 ) , terminator: " " )
115+ fflush ( stdout)
116+
117+ if fraction >= 1.0 {
118+ print ( " " )
119+ self . isDone = true
120+ break
121+ }
122+
123+ do {
124+ try await Task . sleep ( nanoseconds: 100_000_000 ) // 100ms
125+ } catch {
126+ break
127+ }
128+ }
80129 }
81-
82- lastBytes = currentBytes
83- lastUpdate = now
84- }
85-
86- let pct = Int ( fraction * 100 )
87-
88- let barLength = 20
89- let completedBars = min ( barLength, Int ( fraction * Double( barLength) ) )
90- let emptyBars = max ( 0 , barLength - completedBars)
91-
92- var bars = " "
93- if completedBars > 0 {
94- bars += String ( repeating: " = " , count: completedBars - 1 ) + " > "
95- } else {
96- bars += " "
97- }
98- bars += String ( repeating: " " , count: emptyBars)
99-
100- let pctStr = String ( format: " %3d%% " , pct)
101- let spinner = spinnerFrames [ frameIndex]
102- let speedText = " | Speed: \( speedStr) "
103-
104- let msg = String ( format: " \r [mlx-server] Download: [%@] %@ %@ %@ " , bars, pctStr, spinner, speedText)
105-
106- print ( msg. padding ( toLength: 90 , withPad: " " , startingAt: 0 ) , terminator: " " )
107- fflush ( stdout)
108-
109- if fraction >= 1.0 {
110- print ( " " )
111- isDone = true
112130 }
113131 }
114132}
0 commit comments