@@ -46,6 +46,7 @@ type scanModel struct {
4646 token string
4747 parallel int
4848 done bool
49+ isRescan bool // true when running in rescan mode (no upload, just rescan API + poll)
4950}
5051
5152// --- Messages ---
@@ -130,6 +131,16 @@ func delayedPollCmd(index int, web webapi.Service, sha256 string) tea.Cmd {
130131 })
131132}
132133
134+ func rescanFileCmd (index int , web webapi.Service , sha256 , token string ) tea.Cmd {
135+ return func () tea.Msg {
136+ err := web .Rescan (sha256 , token , osFlag , enableDetonationFlag , timeoutFlag )
137+ if err != nil {
138+ return fileUploadedMsg {index : index , err : fmt .Errorf ("rescan: %w" , err )}
139+ }
140+ return fileUploadedMsg {index : index , sha256 : sha256 }
141+ }
142+ }
143+
133144// --- Model interface ---
134145
135146func newScanModel (files []string , web webapi.Service , token string , parallel int ) scanModel {
@@ -154,20 +165,51 @@ func newScanModel(files []string, web webapi.Service, token string, parallel int
154165 }
155166}
156167
168+ func newRescanModel (sha256List []string , web webapi.Service , token string , parallel int ) scanModel {
169+ if parallel < 1 {
170+ parallel = 1
171+ }
172+ rows := make ([]fileRow , len (sha256List ))
173+ for i , sha := range sha256List {
174+ s := spinner .New ()
175+ s .Spinner = spinner .Dot
176+ rows [i ] = fileRow {
177+ filename : sha ,
178+ sha256 : sha ,
179+ state : statePending ,
180+ spinner : s ,
181+ }
182+ }
183+ return scanModel {
184+ files : rows ,
185+ web : web ,
186+ token : token ,
187+ parallel : parallel ,
188+ isRescan : true ,
189+ }
190+ }
191+
157192func (m scanModel ) Init () tea.Cmd {
158193 if len (m .files ) == 0 {
159194 return tea .Quit
160195 }
161196
162- // Launch up to m.parallel uploads concurrently.
197+ // Launch up to m.parallel operations concurrently.
163198 n := min (m .parallel , len (m .files ))
164199 var cmds []tea.Cmd
165200 for i := range n {
166201 m .files [i ].state = stateUploading
167- cmds = append (cmds ,
168- uploadFileCmd (i , m .web , m .files [i ].filename , m .token ),
169- m .files [i ].spinner .Tick ,
170- )
202+ if m .isRescan {
203+ cmds = append (cmds ,
204+ rescanFileCmd (i , m .web , m .files [i ].sha256 , m .token ),
205+ m .files [i ].spinner .Tick ,
206+ )
207+ } else {
208+ cmds = append (cmds ,
209+ uploadFileCmd (i , m .web , m .files [i ].filename , m .token ),
210+ m .files [i ].spinner .Tick ,
211+ )
212+ }
171213 }
172214 return tea .Batch (cmds ... )
173215}
@@ -260,10 +302,17 @@ func (m *scanModel) maybeQuitOrNext() tea.Cmd {
260302 }
261303 if m .files [i ].state == statePending {
262304 m .files [i ].state = stateUploading
263- cmds = append (cmds ,
264- uploadFileCmd (i , m .web , m .files [i ].filename , m .token ),
265- m .files [i ].spinner .Tick ,
266- )
305+ if m .isRescan {
306+ cmds = append (cmds ,
307+ rescanFileCmd (i , m .web , m .files [i ].sha256 , m .token ),
308+ m .files [i ].spinner .Tick ,
309+ )
310+ } else {
311+ cmds = append (cmds ,
312+ uploadFileCmd (i , m .web , m .files [i ].filename , m .token ),
313+ m .files [i ].spinner .Tick ,
314+ )
315+ }
267316 inFlight ++
268317 }
269318 }
@@ -292,11 +341,15 @@ func (m scanModel) View() string {
292341 s += styleDim .Render (" " + name ) + "\n "
293342
294343 case stateUploading :
295- s += f .spinner .View () + styleLabel .Render (" Uploading " ) + name + " ...\n "
344+ label := " Uploading "
345+ if m .isRescan {
346+ label = " Rescanning "
347+ }
348+ s += f .spinner .View () + styleLabel .Render (label ) + name + " ...\n "
296349
297350 case stateScanning :
298351 sha := truncSha (f .sha256 )
299- s += f .spinner .View () + styleLabel .Render (" Scanning " ) + name + " " + styleDim .Render (sha ) + "\n "
352+ s += f .spinner .View () + styleLabel .Render (" Scanning " ) + name + " " + styleDim .Render (sha ) + "\n "
300353
301354 case stateDone :
302355 sha := truncSha (f .sha256 )
0 commit comments