@@ -364,9 +364,31 @@ func (ra *recoveryAdmin) handleStatus(w http.ResponseWriter, r *http.Request, ac
364364 ctx := logtrace .CtxWithCorrelationID (r .Context (), actionID )
365365 ctx = logtrace .CtxWithOrigin (ctx , "recovery_status" )
366366
367+ // "downloadable" semantics: action can be decoded now via normal download flow (network+local).
368+ downloadable , downloadErr , downloadEvents , downloadLastEvent , downloadedFilePath , downloadedDir , err := ra .checkDownloadable (ctx , actionID )
369+ if err != nil {
370+ writeJSON (w , http .StatusInternalServerError , map [string ]any {"ok" : false , "action_id" : actionID , "error" : err .Error ()})
371+ return
372+ }
373+
367374 bundle , err := ra .resolveArtefactBundle (ctx , actionID )
368375 if err != nil {
369- writeJSON (w , http .StatusOK , map [string ]any {"ok" : false , "action_id" : actionID , "error" : err .Error ()})
376+ resp := map [string ]any {
377+ "ok" : false ,
378+ "action_id" : actionID ,
379+ "downloadable" : downloadable ,
380+ "checked_supernode" : ra .selfSupernode ,
381+ "download_events" : downloadEvents ,
382+ "download_last_event" : downloadLastEvent ,
383+ "downloaded_file_path" : downloadedFilePath ,
384+ "downloaded_dir" : downloadedDir ,
385+ "duration_ms" : time .Since (start ).Milliseconds (),
386+ "error" : err .Error (),
387+ }
388+ if downloadErr != "" {
389+ resp ["download_error" ] = downloadErr
390+ }
391+ writeJSON (w , http .StatusOK , resp )
370392 return
371393 }
372394
@@ -387,9 +409,9 @@ func (ra *recoveryAdmin) handleStatus(w http.ResponseWriter, r *http.Request, ac
387409 layoutMax := len (bundle .LayoutIDs )
388410 symbolMax := len (bundle .SymbolKeys )
389411 reachable := 0
390- downloadable := false
391- downloadableFrom := ""
392- downloadableFromAll := make ([]string , 0 , 4 )
412+ fullCopyAvailable := false
413+ fullCopyFrom := ""
414+ fullCopyFromAll := make ([]string , 0 , 4 )
393415 dist := make ([]map [string ]any , 0 , len (results ))
394416 for _ , pr := range results {
395417 if pr .OK {
@@ -400,11 +422,11 @@ func (ra *recoveryAdmin) handleStatus(w http.ResponseWriter, r *http.Request, ac
400422 pr .LayoutFilesPresent == layoutMax &&
401423 pr .SymbolsPresent == symbolMax
402424 if complete {
403- if ! downloadable {
404- downloadableFrom = pr .Target .SupernodeAddress
425+ if ! fullCopyAvailable {
426+ fullCopyFrom = pr .Target .SupernodeAddress
405427 }
406- downloadable = true
407- downloadableFromAll = append (downloadableFromAll , pr .Target .SupernodeAddress )
428+ fullCopyAvailable = true
429+ fullCopyFromAll = append (fullCopyFromAll , pr .Target .SupernodeAddress )
408430 }
409431 dist = append (dist , map [string ]any {
410432 "supernode_address" : pr .Target .SupernodeAddress ,
@@ -420,12 +442,35 @@ func (ra *recoveryAdmin) handleStatus(w http.ResponseWriter, r *http.Request, ac
420442 }
421443
422444 writeJSON (w , http .StatusOK , map [string ]any {
423- "ok" : true ,
424- "action_id" : actionID ,
425- "duration_ms" : time .Since (start ).Milliseconds (),
426- "downloadable" : downloadable ,
427- "downloadable_from_supernode" : downloadableFrom ,
428- "downloadable_from_supernodes" : downloadableFromAll ,
445+ "ok" : true ,
446+ "action_id" : actionID ,
447+ "duration_ms" : time .Since (start ).Milliseconds (),
448+ "downloadable" : downloadable ,
449+ "downloadable_from_supernode" : func () string {
450+ if downloadable {
451+ return ra .selfSupernode
452+ }
453+ return ""
454+ }(),
455+ "downloadable_from_supernodes" : func () []string {
456+ if downloadable {
457+ return []string {ra .selfSupernode }
458+ }
459+ return []string {}
460+ }(),
461+ "download_events" : downloadEvents ,
462+ "download_last_event" : downloadLastEvent ,
463+ "download_error" : downloadErr ,
464+ "downloaded_file_path" : downloadedFilePath ,
465+ "downloaded_dir" : downloadedDir ,
466+ "full_copy_available" : fullCopyAvailable ,
467+ "full_copy_from_supernode" : func () string {
468+ if fullCopyAvailable {
469+ return fullCopyFrom
470+ }
471+ return ""
472+ }(),
473+ "full_copy_from_supernodes" : fullCopyFromAll ,
429474 "totals" : map [string ]any {
430475 "index_files_total" : indexMax ,
431476 "layout_files_total" : layoutMax ,
@@ -440,6 +485,31 @@ func (ra *recoveryAdmin) handleStatus(w http.ResponseWriter, r *http.Request, ac
440485 })
441486}
442487
488+ func (ra * recoveryAdmin ) checkDownloadable (ctx context.Context , actionID string ) (ok bool , downloadErr string , events int , lastEvent string , downloadedFilePath string , downloadedDir string , err error ) {
489+ task := ra .cascadeFactory .NewCascadeRegistrationTask ()
490+ crt , castOK := task .(* cascadeService.CascadeRegistrationTask )
491+ if ! castOK {
492+ return false , "" , 0 , "" , "" , "" , fmt .Errorf ("unexpected task type" )
493+ }
494+
495+ dlErr := crt .Download (ctx , & cascadeService.DownloadRequest {
496+ ActionID : actionID ,
497+ BypassPrivateSignature : true ,
498+ }, func (resp * cascadeService.DownloadResponse ) error {
499+ events ++
500+ lastEvent = fmt .Sprintf ("%d:%s" , resp .EventType , resp .Message )
501+ if resp .EventType == cascadeService .SupernodeEventTypeDecodeCompleted {
502+ downloadedDir = resp .DownloadedDir
503+ downloadedFilePath = resp .FilePath
504+ }
505+ return nil
506+ })
507+ if dlErr != nil {
508+ return false , dlErr .Error (), events , lastEvent , downloadedFilePath , downloadedDir , nil
509+ }
510+ return true , "" , events , lastEvent , downloadedFilePath , downloadedDir , nil
511+ }
512+
443513func (ra * recoveryAdmin ) handleInternalProbe (w http.ResponseWriter , r * http.Request ) {
444514 if r .Method != http .MethodPost {
445515 writeJSON (w , http .StatusMethodNotAllowed , map [string ]any {"ok" : false , "error" : "method not allowed" })
@@ -562,13 +632,13 @@ func (ra *recoveryAdmin) resolveArtefactBundle(ctx context.Context, actionID str
562632 return artefactBundle {}, fmt .Errorf ("parse layout_id %s: %v" , lid , perr )
563633 }
564634 for _ , blk := range layout .Blocks {
565- prefix := fmt .Sprintf ("block_%d" , blk .BlockID )
566635 for _ , sid := range blk .Symbols {
567636 sid = strings .TrimSpace (sid )
568637 if sid == "" {
569638 continue
570639 }
571- symbolSet [prefix + "/" + sid ] = struct {}{}
640+ // DHT symbol keys are raw symbol IDs (base58), not path-like block prefixes.
641+ symbolSet [sid ] = struct {}{}
572642 }
573643 }
574644 }
0 commit comments