74 lines
2.0 KiB
Go
74 lines
2.0 KiB
Go
package cache
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
type SnapshotOptions struct {
|
|
SourcePath string
|
|
CacheDir string
|
|
Name string
|
|
MaxFileBytes int64
|
|
Now func() time.Time
|
|
}
|
|
|
|
type Snapshot struct {
|
|
SourcePath string `json:"source_path"`
|
|
Path string `json:"path"`
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
func SnapshotFile(opts SnapshotOptions) (Snapshot, error) {
|
|
if opts.SourcePath == "" {
|
|
return Snapshot{}, errors.New("source path is required")
|
|
}
|
|
if opts.CacheDir == "" {
|
|
return Snapshot{}, errors.New("cache dir is required")
|
|
}
|
|
info, err := os.Stat(opts.SourcePath)
|
|
if err != nil {
|
|
return Snapshot{}, fmt.Errorf("stat source: %w", err)
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return Snapshot{}, fmt.Errorf("source is not a regular file: %s", opts.SourcePath)
|
|
}
|
|
if opts.MaxFileBytes > 0 && info.Size() > opts.MaxFileBytes {
|
|
return Snapshot{}, fmt.Errorf("source file is %d bytes, exceeds limit %d", info.Size(), opts.MaxFileBytes)
|
|
}
|
|
now := opts.Now
|
|
if now == nil {
|
|
now = func() time.Time { return time.Now().UTC() }
|
|
}
|
|
if err := os.MkdirAll(opts.CacheDir, 0o755); err != nil {
|
|
return Snapshot{}, fmt.Errorf("create cache dir: %w", err)
|
|
}
|
|
name := opts.Name
|
|
if name == "" {
|
|
name = filepath.Base(opts.SourcePath)
|
|
}
|
|
target := filepath.Join(opts.CacheDir, now().UTC().Format("20060102T150405Z")+"-"+name)
|
|
src, err := os.Open(opts.SourcePath)
|
|
if err != nil {
|
|
return Snapshot{}, fmt.Errorf("open source: %w", err)
|
|
}
|
|
defer src.Close()
|
|
dst, err := os.OpenFile(target, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600)
|
|
if err != nil {
|
|
return Snapshot{}, fmt.Errorf("create snapshot: %w", err)
|
|
}
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
_ = dst.Close()
|
|
return Snapshot{}, fmt.Errorf("copy snapshot: %w", err)
|
|
}
|
|
if err := dst.Close(); err != nil {
|
|
return Snapshot{}, fmt.Errorf("close snapshot: %w", err)
|
|
}
|
|
return Snapshot{SourcePath: opts.SourcePath, Path: target, SizeBytes: info.Size(), CreatedAt: now().UTC()}, nil
|
|
}
|