feat: base go mkiso
This commit is contained in:
120
internal/service/relay.go
Normal file
120
internal/service/relay.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user