121 lines
3.5 KiB
Go
121 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"mkiso-server/internal/config"
|
|
"mkiso-server/internal/repo"
|
|
"mkiso-server/pkg/dicom"
|
|
)
|
|
|
|
// RelayService handles DICOM relay (print) to the CD Publisher.
|
|
type RelayService struct {
|
|
cfg *config.Config
|
|
dicomSvc *DicomService
|
|
patientRepo *repo.PatientRepo
|
|
}
|
|
|
|
// NewRelayService creates a new RelayService.
|
|
func NewRelayService(cfg *config.Config, dicomSvc *DicomService, patientRepo *repo.PatientRepo) *RelayService {
|
|
return &RelayService{
|
|
cfg: cfg,
|
|
dicomSvc: dicomSvc,
|
|
patientRepo: patientRepo,
|
|
}
|
|
}
|
|
|
|
// Destination returns the CD Publisher address as "host:port".
|
|
func (s *RelayService) Destination() string {
|
|
return fmt.Sprintf("%s:%d", s.cfg.CDPublisher.Host, s.cfg.CDPublisher.Port)
|
|
}
|
|
|
|
// RelayResult describes the outcome of a DICOM relay operation.
|
|
type RelayResult struct {
|
|
AccessionsSent []string `json:"accessions_sent"`
|
|
PatientName string `json:"patient_name"`
|
|
Destination string `json:"destination"`
|
|
FilesSent int `json:"files_sent"`
|
|
}
|
|
|
|
// RelayToCDPublisher fetches DICOM studies from PACS and forwards them
|
|
// to the CD Publisher via storescu (C-STORE).
|
|
func (s *RelayService) RelayToCDPublisher(ctx context.Context, accessionNumbers []string) (*RelayResult, error) {
|
|
// Step 1: Try to get patient data from API (graceful if unavailable)
|
|
var patientName string
|
|
patient, err := s.patientRepo.ByAccessionNumber(ctx, accessionNumbers[0])
|
|
if err != nil {
|
|
slog.Warn("patient API lookup failed for relay",
|
|
"accession", accessionNumbers[0],
|
|
"error", err,
|
|
)
|
|
} else if patient != nil {
|
|
patientName = patient.PatientName
|
|
slog.Info("patient data retrieved for relay",
|
|
"accession", accessionNumbers[0],
|
|
"patient", patientName,
|
|
)
|
|
}
|
|
|
|
// Step 2: Create temp dir
|
|
tempDir, err := os.MkdirTemp(s.cfg.ISO.TempDir, "dicomdir_")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create temp dir: %w", err)
|
|
}
|
|
defer os.RemoveAll(tempDir) // guaranteed cleanup
|
|
|
|
dicomDir := filepath.Join(tempDir, "DICOMDIR")
|
|
if err := os.MkdirAll(dicomDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("create DICOMDIR: %w", err)
|
|
}
|
|
|
|
// Copy microdicom viewer files (optional — CD Publisher doesn't need it,
|
|
// but matches PHP behavior of copying before fetch)
|
|
if dicom.DirExists(s.cfg.ISO.MicrodicomPath) {
|
|
if err := dicom.CopyDir(s.cfg.ISO.MicrodicomPath, tempDir); err != nil {
|
|
slog.Warn("copy microdicom for relay failed", "error", err)
|
|
}
|
|
}
|
|
|
|
// Step 3: Fetch DICOM from PACS
|
|
filesRetrieved, err := s.dicomSvc.FetchDICOMMultiple(ctx, accessionNumbers, dicomDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("DICOM fetch failed: %w", err)
|
|
}
|
|
slog.Info("DICOM fetched for relay",
|
|
"accessions", accessionNumbers,
|
|
"files", filesRetrieved,
|
|
)
|
|
|
|
// Step 4: Relay to CD Publisher via storescu
|
|
destination := fmt.Sprintf("%s:%d", s.cfg.CDPublisher.Host, s.cfg.CDPublisher.Port)
|
|
exitCode, stdout, stderr, err := dicom.RunStoreSCU(
|
|
ctx,
|
|
s.cfg.DCMTK.Storescu,
|
|
s.cfg.OurAE.AETitle,
|
|
s.cfg.CDPublisher.Host,
|
|
s.cfg.CDPublisher.Port,
|
|
dicomDir,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("storescu relay failed (exit %d): %w (stderr: %s)", exitCode, err, stderr)
|
|
}
|
|
_ = stdout
|
|
|
|
slog.Info("DICOM relayed to CD Publisher",
|
|
"accessions", accessionNumbers,
|
|
"destination", destination,
|
|
"files", filesRetrieved,
|
|
)
|
|
|
|
return &RelayResult{
|
|
AccessionsSent: accessionNumbers,
|
|
PatientName: patientName,
|
|
Destination: destination,
|
|
FilesSent: filesRetrieved,
|
|
}, nil
|
|
}
|