@@ -309,7 +309,7 @@ func (c *Client) GetHistoryByDownloadID(ctx context.Context, baseURL string, api
309309type CommandResponse struct {
310310 ID int `json:"id"`
311311 Name string `json:"name"`
312- Status string `json:"status"`
312+ Status string `json:"status"` // Possible values: queued, started, completed, failed
313313}
314314
315315// RefreshMonitoredDownloads triggers Sonarr/Radarr to refresh the download queue
@@ -361,10 +361,163 @@ func (c *Client) RefreshMonitoredDownloads(ctx context.Context, baseURL string,
361361 return fmt .Errorf ("decode response: %w" , err )
362362 }
363363
364- c .logger .Info ("RefreshMonitoredDownloads command response " ,
364+ c .logger .Info ("RefreshMonitoredDownloads command triggered " ,
365365 zap .Int ("commandId" , cmdResp .ID ),
366366 zap .String ("name" , cmdResp .Name ),
367367 zap .String ("status" , cmdResp .Status ))
368368
369369 return nil
370370}
371+
372+ // GetCommandStatus retrieves the status of a command by ID
373+ func (c * Client ) GetCommandStatus (ctx context.Context , baseURL string , apiKey string , commandID int ) (* CommandResponse , error ) {
374+ parsedURL , err := url .Parse (baseURL )
375+ if err != nil {
376+ return nil , fmt .Errorf ("invalid base URL: %w" , err )
377+ }
378+
379+ parsedURL .Path = fmt .Sprintf ("%s/api/v3/command/%d" , parsedURL .Path , commandID )
380+
381+ req , err := http .NewRequestWithContext (ctx , "GET" , parsedURL .String (), nil )
382+ if err != nil {
383+ return nil , fmt .Errorf ("create request: %w" , err )
384+ }
385+
386+ req .Header .Set ("X-Api-Key" , apiKey )
387+
388+ resp , err := c .httpClient .Do (req )
389+ if err != nil {
390+ return nil , fmt .Errorf ("request failed: %w" , err )
391+ }
392+ defer resp .Body .Close ()
393+
394+ if resp .StatusCode == http .StatusUnauthorized {
395+ return nil , fmt .Errorf ("unauthorized: check API key" )
396+ }
397+
398+ if resp .StatusCode != http .StatusOK {
399+ return nil , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
400+ }
401+
402+ var cmdResp CommandResponse
403+ if err := json .NewDecoder (resp .Body ).Decode (& cmdResp ); err != nil {
404+ return nil , fmt .Errorf ("decode response: %w" , err )
405+ }
406+
407+ return & cmdResp , nil
408+ }
409+
410+ // RefreshMonitoredDownloadsAndWait triggers a refresh and waits for it to complete
411+ func (c * Client ) RefreshMonitoredDownloadsAndWait (ctx context.Context , baseURL string , apiKey string , maxWaitSeconds int ) error {
412+ parsedURL , err := url .Parse (baseURL )
413+ if err != nil {
414+ return fmt .Errorf ("invalid base URL: %w" , err )
415+ }
416+
417+ parsedURL .Path = parsedURL .Path + "/api/v3/command"
418+
419+ // Command payload
420+ payload := map [string ]string {
421+ "name" : "RefreshMonitoredDownloads" ,
422+ }
423+
424+ jsonData , err := json .Marshal (payload )
425+ if err != nil {
426+ return fmt .Errorf ("marshal payload: %w" , err )
427+ }
428+
429+ req , err := http .NewRequestWithContext (ctx , "POST" , parsedURL .String (), strings .NewReader (string (jsonData )))
430+ if err != nil {
431+ return fmt .Errorf ("create request: %w" , err )
432+ }
433+
434+ req .Header .Set ("X-Api-Key" , apiKey )
435+ req .Header .Set ("Content-Type" , "application/json" )
436+
437+ c .logger .Debug ("triggering RefreshMonitoredDownloads" ,
438+ zap .String ("url" , parsedURL .Host ))
439+
440+ resp , err := c .httpClient .Do (req )
441+ if err != nil {
442+ return fmt .Errorf ("request failed: %w" , err )
443+ }
444+ defer resp .Body .Close ()
445+
446+ if resp .StatusCode == http .StatusUnauthorized {
447+ return fmt .Errorf ("unauthorized: check API key" )
448+ }
449+
450+ if resp .StatusCode != http .StatusCreated && resp .StatusCode != http .StatusOK {
451+ return fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
452+ }
453+
454+ var cmdResp CommandResponse
455+ if err := json .NewDecoder (resp .Body ).Decode (& cmdResp ); err != nil {
456+ return fmt .Errorf ("decode response: %w" , err )
457+ }
458+
459+ c .logger .Info ("RefreshMonitoredDownloads command triggered, waiting for completion" ,
460+ zap .Int ("commandId" , cmdResp .ID ),
461+ zap .String ("name" , cmdResp .Name ),
462+ zap .String ("initialStatus" , cmdResp .Status ),
463+ zap .Int ("maxWaitSeconds" , maxWaitSeconds ))
464+
465+ // Poll command status until completed or timeout
466+ ticker := time .NewTicker (500 * time .Millisecond ) // Poll every 500ms
467+ defer ticker .Stop ()
468+
469+ deadline := time .Now ().Add (time .Duration (maxWaitSeconds ) * time .Second )
470+
471+ for {
472+ select {
473+ case <- ctx .Done ():
474+ return fmt .Errorf ("context cancelled while waiting for command completion" )
475+
476+ case <- ticker .C :
477+ // Check if we've exceeded the deadline
478+ if time .Now ().After (deadline ) {
479+ c .logger .Warn ("RefreshMonitoredDownloads command did not complete in time" ,
480+ zap .Int ("commandId" , cmdResp .ID ),
481+ zap .Int ("maxWaitSeconds" , maxWaitSeconds ))
482+ return fmt .Errorf ("command did not complete within %d seconds" , maxWaitSeconds )
483+ }
484+
485+ // Get current command status
486+ status , err := c .GetCommandStatus (ctx , baseURL , apiKey , cmdResp .ID )
487+ if err != nil {
488+ c .logger .Warn ("failed to get command status, will retry" ,
489+ zap .Int ("commandId" , cmdResp .ID ),
490+ zap .Error (err ))
491+ continue
492+ }
493+
494+ c .logger .Debug ("RefreshMonitoredDownloads command status" ,
495+ zap .Int ("commandId" , cmdResp .ID ),
496+ zap .String ("status" , status .Status ))
497+
498+ // Check if command is completed
499+ switch status .Status {
500+ case "completed" :
501+ c .logger .Info ("RefreshMonitoredDownloads command completed" ,
502+ zap .Int ("commandId" , cmdResp .ID ),
503+ zap .Duration ("elapsed" , time .Since (time .Now ().Add (- time .Duration (maxWaitSeconds )* time .Second ))))
504+ return nil
505+
506+ case "failed" :
507+ c .logger .Error ("RefreshMonitoredDownloads command failed" ,
508+ zap .Int ("commandId" , cmdResp .ID ))
509+ return fmt .Errorf ("command failed" )
510+
511+ case "queued" , "started" :
512+ // Still in progress, continue polling
513+ continue
514+
515+ default :
516+ c .logger .Warn ("RefreshMonitoredDownloads command unknown status" ,
517+ zap .Int ("commandId" , cmdResp .ID ),
518+ zap .String ("status" , status .Status ))
519+ continue
520+ }
521+ }
522+ }
523+ }
0 commit comments