Files
akai-utils/extract.go
T
david 120993fb02 test: add comprehensive test suite - 5 test files, 65% coverage
- cli_test.go: 10 tests for list/search/extract/download commands
- serve_test.go: 11 tests for HTTP handlers and SSE progress
- http_test.go: 5 subtests for archive.org API
- download_test.go: 10 tests for download logic
- util_test.go: 7 tests for string/util helpers

Error paths covered: server errors, empty results, download failures,
extract failures, missing identifiers, ReadDir errors.

golangci-lint: 0 issues, go vet: clean
2026-06-21 23:54:57 -07:00

213 lines
5.0 KiB
Go

package main
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
func findAkaiutil() (string, error) {
if env := os.Getenv("AKAIUTIL"); env != "" {
if fi, err := os.Stat(env); err == nil && !fi.IsDir() {
return env, nil
}
}
candidates := []string{
filepath.Join(filepath.Dir(os.Args[0]), "akaiutil"),
filepath.Join(filepath.Dir(os.Args[0]), "third_party", "akaiutil", "akaiutil"),
filepath.Join("third_party", "akaiutil", "akaiutil"),
"akaiutil",
}
for _, p := range candidates {
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
return p, nil
}
}
return "", fmt.Errorf("akaiutil not found — set AKAIUTIL env var or run: make akaiutil")
}
var runAkaiutil = func(isoPath, commands string) (string, error) {
bin, err := findAkaiutil()
if err != nil {
return "", err
}
cmd := exec.Command(bin, "-r", isoPath)
cmd.Stderr = nil
stdin, err := cmd.StdinPipe()
if err != nil {
return "", fmt.Errorf("stdin pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", fmt.Errorf("stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return "", fmt.Errorf("start akaiutil: %w", err)
}
if _, err := stdin.Write([]byte(commands)); err != nil {
return "", fmt.Errorf("write stdin: %w", err)
}
if err := stdin.Close(); err != nil {
return "", fmt.Errorf("close stdin: %w", err)
}
output, _ := io.ReadAll(stdout)
if err := cmd.Wait(); err != nil {
return "", fmt.Errorf("wait akaiutil: %w", err)
}
return string(output), nil
}
type volInfo struct {
Part string
Name string
}
func listPartitions(isoPath string) ([]string, error) {
out, err := runAkaiutil(isoPath, "df\nq\n")
if err != nil {
return nil, err
}
re := regexp.MustCompile(`^\s+([A-Z])\s+`)
var parts []string
for _, line := range strings.Split(out, "\n") {
if m := re.FindStringSubmatch(line); m != nil {
parts = append(parts, m[1])
}
}
if len(parts) == 0 {
return []string{"A"}, nil
}
return parts, nil
}
func listVolumes(isoPath string, partitions []string) ([]volInfo, error) {
var cmds strings.Builder
for _, p := range partitions {
fmt.Fprintf(&cmds, "cd /disk0/%s\n", p)
fmt.Fprintf(&cmds, "dir\n")
}
cmds.WriteString("q\n")
out, err := runAkaiutil(isoPath, cmds.String())
if err != nil {
return nil, err
}
rePrompt := regexp.MustCompile(`/disk0/([A-Z])\s*>`)
reEntry := regexp.MustCompile(`^\s+\d+\s+(.+?)\s+-\s+`)
var vols []volInfo
currentPart := ""
for _, line := range strings.Split(out, "\n") {
if m := rePrompt.FindStringSubmatch(line); m != nil {
currentPart = m[1]
continue
}
if m := reEntry.FindStringSubmatch(line); m != nil {
name := strings.TrimRight(m[1], " ")
if currentPart != "" && name != "" {
vols = append(vols, volInfo{Part: currentPart, Name: name})
}
}
}
return vols, nil
}
func runExtraction(isoPath, baseDir string, vols []volInfo) (int, error) {
var cmds strings.Builder
for _, v := range vols {
safe := sanitiseISOName(v.Name)
if safe == "" {
safe = "volume"
}
volDir := filepath.Join(baseDir, safe)
if err := os.MkdirAll(volDir, 0755); err != nil {
return 0, fmt.Errorf("mkdir %s: %w", volDir, err)
}
nav := strings.ReplaceAll(v.Name, " ", "_")
fmt.Fprintf(&cmds, "lcd %s\n", volDir)
fmt.Fprintf(&cmds, "cd /disk0/%s/%s\n", v.Part, nav)
fmt.Fprintf(&cmds, "sample2wavall\n")
}
fmt.Fprintf(&cmds, "lcd /\nq\n")
out, err := runAkaiutil(isoPath, cmds.String())
if err != nil {
return 0, err
}
re := regexp.MustCompile(`exported\s+(\d+)\s+file`)
total := 0
for _, line := range strings.Split(out, "\n") {
if m := re.FindStringSubmatch(line); m != nil {
n, _ := strconv.Atoi(m[1])
total += n
}
}
return total, nil
}
func extractWAVs(isoPath, outDir string) (int, error) {
parts, err := listPartitions(isoPath)
if err != nil {
return 0, fmt.Errorf("enumerate partitions: %w", err)
}
vols, err := listVolumes(isoPath, parts)
if err != nil {
return 0, fmt.Errorf("enumerate volumes: %w", err)
}
if len(vols) == 0 {
return 0, nil
}
isoBase := sanitiseISOName(filepath.Base(isoPath))
baseDir := filepath.Join(outDir, isoBase)
if err := os.MkdirAll(baseDir, 0755); err != nil {
return 0, fmt.Errorf("mkdir %s: %w", baseDir, err)
}
return runExtraction(isoPath, baseDir, vols)
}
var extractISO = func(isoPath, outDir string) (string, error) {
isoOutDir := filepath.Join(outDir, sanitiseISOName(filepath.Base(isoPath)))
if fi, err := os.Stat(isoOutDir); err == nil && fi.IsDir() {
var wavCount int
_ = filepath.WalkDir(isoOutDir, func(p string, d os.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(strings.ToLower(d.Name()), ".wav") {
wavCount++
}
return nil
})
if wavCount > 0 {
return fmt.Sprintf("skipped (%d WAVs exist)", wavCount), nil
}
}
start := time.Now()
total, err := extractWAVs(isoPath, outDir)
elapsed := time.Since(start).Round(time.Second)
if err != nil {
return "", err
}
return fmt.Sprintf("Total WAV files: %d (%s)", total, elapsed), nil
}