6.3 KiB
6.3 KiB
Detail: genisoimage → go-diskfs
1. internal/isobuilder/builder.go — new file
package isobuilder
import (
"fmt"
"io"
"os"
"path/filepath"
diskfs "github.com/diskfs/go-diskfs"
"github.com/diskfs/go-diskfs/disk"
"github.com/diskfs/go-diskfs/filesystem"
"github.com/diskfs/go-diskfs/filesystem/iso9660"
)
// BuildFromDirectory creates an ISO 9660 image from a source directory.
// Equivalent to: genisoimage -iso-level 4 -r -J -V <label> -o <isoPath> <srcDir>
func BuildFromDirectory(srcDir, isoPath, volumeLabel string) error {
// Step 1: Calculate total size (sum of all files + 10% overhead)
totalSize, err := dirSize(srcDir)
if err != nil {
return fmt.Errorf("calculate directory size: %w", err)
}
// Step 2: Create disk image
mydisk, err := diskfs.Create(isoPath, totalSize, diskfs.SectorSizeDefault)
if err != nil {
return fmt.Errorf("create disk image: %w", err)
}
mydisk.LogicalBlocksize = 2048
// Step 3: Create ISO 9660 filesystem
fs, err := mydisk.CreateFilesystem(disk.FilesystemSpec{
Partition: 0,
FSType: filesystem.TypeISO9660,
VolumeLabel: volumeLabel,
})
if err != nil {
return fmt.Errorf("create ISO filesystem: %w", err)
}
defer fs.Close()
// Step 4: Walk source dir and copy files
err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
if relPath == "." {
return nil // skip root
}
if info.IsDir() {
if err := fs.Mkdir(relPath); err != nil {
return fmt.Errorf("mkdir %q: %w", relPath, err)
}
return nil
}
// Regular file — copy contents
rw, err := fs.OpenFile(relPath, os.O_CREATE|os.O_RDWR)
if err != nil {
return fmt.Errorf("open file %q: %w", relPath, err)
}
defer rw.Close()
srcFile, err := os.Open(path)
if err != nil {
return fmt.Errorf("open source %q: %w", path, err)
}
defer srcFile.Close()
if _, err := io.Copy(rw, srcFile); err != nil {
return fmt.Errorf("copy %q: %w", relPath, err)
}
return nil
})
if err != nil {
return fmt.Errorf("walk source directory: %w", err)
}
// Step 5: Finalize with Rock Ridge + Joliet
iso, ok := fs.(*iso9660.FileSystem)
if !ok {
return fmt.Errorf("not an ISO 9660 filesystem")
}
if err := iso.Finalize(iso9660.FinalizeOptions{
RockRidge: true,
Joliet: true,
DeepDirectories: true,
VolumeIdentifier: volumeLabel,
}); err != nil {
return fmt.Errorf("finalize ISO: %w", err)
}
return nil
}
// dirSize calculates total size of all files in a directory tree,
// with 10% overhead margin for ISO metadata.
func dirSize(dir string) (int64, error) {
var total int64
err := filepath.Walk(dir, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
total += info.Size()
}
return nil
})
if err != nil {
return 0, err
}
// Add 10% overhead for ISO 9660 metadata
total = total + total/10
// Minimum 10MB (some PACS studies are small)
if total < 10*1024*1024 {
total = 10 * 1024 * 1024
}
// Round up to next 2048-byte sector
if total%2048 != 0 {
total = total + (2048 - total%2048)
}
return total, nil
}
2. internal/config/config.go — remove Tools field
Current (line ~31):
type Config struct {
Server ServerConfig `yaml:"server"`
Auth AuthConfig `yaml:"auth"`
DCMTK DCMTKConfig `yaml:"dcmtk"`
Tools ToolsConfig `yaml:"tools"`
...
}
Remove Tools ToolsConfig field and entire ToolsConfig struct.
Current validation (line ~82):
if c.Tools.Genisoimage == "" {
return fmt.Errorf("tools.genisoimage is required")
}
Remove the above lines.
3. config.example.yaml — remove tools: block
Remove lines:
tools:
genisoimage: "/usr/bin/genisoimage"
4. internal/service/iso.go — replace genisoimage call
Current in GenerateISO() (line ~68-78):
isoPath := filepath.Join(s.cfg.ISO.TempDir, isoName)
exitCode, stdout, stderr, err := dicom.RunGenISOImage(ctx, s.cfg.Tools.Genisoimage, isoPath, tempDir)
if err != nil || exitCode != 0 {
cleanup()
os.Remove(isoPath)
return nil, fmt.Errorf("genisoimage failed (exit %d): %s (stderr: %s)", exitCode, err, stderr)
}
_ = stdout
slog.Info("ISO created",
"path", isoPath,
"accession", accessionNumber,
)
Replace with:
isoPath := filepath.Join(s.cfg.ISO.TempDir, isoName)
if err := isobuilder.BuildFromDirectory(tempDir, isoPath, "DICOM"); err != nil {
cleanup()
os.Remove(isoPath)
return nil, fmt.Errorf("ISO creation failed: %w", err)
}
slog.Info("ISO created",
"path", isoPath,
"accession", accessionNumber,
)
Same replacement in GenerateISOMultiple() — find the second dicom.RunGenISOImage call and replace identically.
Also add import: "mkiso-server/internal/isobuilder"
5. internal/handler/health.go — remove genisoimage from deps
Current (line ~19):
deps := []struct {
Name string `json:"name"`
Path string `json:"path"`
Status string `json:"status"`
}{
{"storescp", cfg.DCMTK.Storescp, ""},
{"movescu", cfg.DCMTK.Movescu, ""},
{"storescu", cfg.DCMTK.Storescu, ""},
{"genisoimage", cfg.Tools.Genisoimage, ""},
}
Replace with (remove genisoimage entry, remove cfg.Tools reference):
deps := []struct {
Name string `json:"name"`
Path string `json:"path"`
Status string `json:"status"`
}{
{"storescp", cfg.DCMTK.Storescp, ""},
{"movescu", cfg.DCMTK.Movescu, ""},
{"storescu", cfg.DCMTK.Storescu, ""},
}
6. pkg/dicom/command.go — remove RunGenISOImage
Delete the entire RunGenISOImage function (lines ~150-180).
7. go.mod — after go get
Will add: github.com/diskfs/go-diskfs v1.9.3 plus its transitive deps.