bsdaily/internal/bsdaily/dump.go
2026-02-09 00:42:06 -08:00

80 lines
1.9 KiB
Go

package bsdaily
import (
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
)
func DumpAndCompress(dbPath, outputPath string) (err error) {
for _, tool := range []string{"sqlite3", "zstdmt"} {
if _, err := exec.LookPath(tool); err != nil {
return fmt.Errorf("required tool %q not found in PATH: %w", tool, err)
}
}
if err := CheckFreeSpace(filepath.Dir(outputPath), MinDailiesFreeBytes, "dailiesBase (pre-dump)"); err != nil {
return err
}
outFile, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("creating output file: %w", err)
}
defer func() {
if cerr := outFile.Close(); cerr != nil && err == nil {
err = fmt.Errorf("closing output: %w", cerr)
}
}()
dumpCmd := exec.Command("sqlite3", dbPath, ".dump")
zstdCmd := exec.Command("zstdmt", "-15")
pipe, err := dumpCmd.StdoutPipe()
if err != nil {
return fmt.Errorf("creating dump stdout pipe: %w", err)
}
zstdCmd.Stdin = pipe
zstdCmd.Stdout = outFile
var dumpStderr, zstdStderr strings.Builder
dumpCmd.Stderr = &dumpStderr
zstdCmd.Stderr = &zstdStderr
slog.Info("starting sqlite3 dump and zstdmt compression")
if err := zstdCmd.Start(); err != nil {
return fmt.Errorf("starting zstdmt: %w", err)
}
if err := dumpCmd.Start(); err != nil {
return fmt.Errorf("starting sqlite3 dump: %w", err)
}
if err := dumpCmd.Wait(); err != nil {
return fmt.Errorf("sqlite3 dump failed: %w; stderr: %s", err, dumpStderr.String())
}
if err := zstdCmd.Wait(); err != nil {
return fmt.Errorf("zstdmt failed: %w; stderr: %s", err, zstdStderr.String())
}
if err := outFile.Sync(); err != nil {
return fmt.Errorf("syncing output: %w", err)
}
info, err := os.Stat(outputPath)
if err != nil {
return fmt.Errorf("stat output: %w", err)
}
slog.Info("compressed output written", "path", outputPath,
"size_bytes", info.Size(), "size_mb", info.Size()/(1024*1024))
if info.Size() == 0 {
return fmt.Errorf("compressed output is empty")
}
return nil
}