@@ -164,6 +164,98 @@ func (a *cascadeAdapter) CascadeSupernodeRegister(ctx context.Context, in *Casca
164164 }, nil
165165}
166166
167+ // CascadeSupernodeDownload downloads a file from a supernode gRPC stream
168+ func (a * cascadeAdapter ) CascadeSupernodeDownload (
169+ ctx context.Context ,
170+ in * CascadeSupernodeDownloadRequest ,
171+ opts ... grpc.CallOption ,
172+ ) (* CascadeSupernodeDownloadResponse , error ) {
173+
174+ ctx = net .AddCorrelationID (ctx )
175+
176+ // 1. Open gRPC stream (server-stream)
177+ stream , err := a .client .Download (ctx , & cascade.DownloadRequest {
178+ ActionId : in .ActionID ,
179+ }, opts ... )
180+ if err != nil {
181+ a .logger .Error (ctx , "failed to create download stream" ,
182+ "action_id" , in .ActionID , "error" , err )
183+ return nil , err
184+ }
185+
186+ // 2. Prepare destination file
187+ outFile , err := os .Create (in .OutputPath )
188+ if err != nil {
189+ a .logger .Error (ctx , "failed to create output file" ,
190+ "path" , in .OutputPath , "error" , err )
191+ return nil , fmt .Errorf ("create output file: %w" , err )
192+ }
193+ defer outFile .Close ()
194+
195+ var (
196+ bytesWritten int64
197+ chunkIndex int
198+ )
199+
200+ // 3. Receive streamed responses
201+ for {
202+ resp , err := stream .Recv ()
203+ if err == io .EOF {
204+ break
205+ }
206+ if err != nil {
207+ return nil , fmt .Errorf ("stream recv: %w" , err )
208+ }
209+
210+ switch x := resp .ResponseType .(type ) {
211+
212+ // 3a. Progress / event message
213+ case * cascade.DownloadResponse_Event :
214+ a .logger .Info (ctx , "supernode event" ,
215+ "event_type" , x .Event .EventType ,
216+ "message" , x .Event .Message ,
217+ "action_id" , in .ActionID )
218+
219+ if in .EventLogger != nil {
220+ in .EventLogger (ctx , toSdkEvent (x .Event .EventType ), x .Event .Message , event.EventData {
221+ event .KeyActionID : in .ActionID ,
222+ event .KeyEventType : x .Event .EventType ,
223+ event .KeyMessage : x .Event .Message ,
224+ })
225+ }
226+
227+ // 3b. Actual data chunk
228+ case * cascade.DownloadResponse_Chunk :
229+ data := x .Chunk .Data
230+ if len (data ) == 0 {
231+ continue
232+ }
233+ if _ , err := outFile .Write (data ); err != nil {
234+ return nil , fmt .Errorf ("write chunk: %w" , err )
235+ }
236+
237+ bytesWritten += int64 (len (data ))
238+ chunkIndex ++
239+
240+ a .logger .Debug (ctx , "received chunk" ,
241+ "chunk_index" , chunkIndex ,
242+ "chunk_size" , len (data ),
243+ "bytes_written" , bytesWritten )
244+ }
245+ }
246+
247+ a .logger .Info (ctx , "download complete" ,
248+ "bytes_written" , bytesWritten ,
249+ "path" , in .OutputPath ,
250+ "action_id" , in .ActionID )
251+
252+ return & CascadeSupernodeDownloadResponse {
253+ Success : true ,
254+ Message : "artefact downloaded" ,
255+ OutputPath : in .OutputPath ,
256+ }, nil
257+ }
258+
167259// toSdkEvent converts a supernode-side enum value into an internal SDK EventType.
168260func toSdkEvent (e cascade.SupernodeEventType ) event.EventType {
169261 switch e {
@@ -189,6 +281,8 @@ func toSdkEvent(e cascade.SupernodeEventType) event.EventType {
189281 return event .SupernodeArtefactsStored
190282 case cascade .SupernodeEventType_ACTION_FINALIZED :
191283 return event .SupernodeActionFinalized
284+ case cascade .SupernodeEventType_ARTEFACTS_DOWNLOADED :
285+ return event .SupernodeArtefactsDownloaded
192286 default :
193287 return event .SupernodeUnknown
194288 }
0 commit comments