package sync import ( "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" ) // BlobDir returns the path to .verstak/blobs/ inside the vault. func BlobDir(vaultRoot string) string { return filepath.Join(vaultRoot, ".verstak", "blobs") } // BlobPath returns the on-disk path for a SHA-256 hash. func BlobPath(blobsDir, shaHex string) string { return filepath.Join(blobsDir, shaHex[:2], shaHex[2:4], shaHex) } // HashFile computes SHA-256 of a file. func HashFile(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := sha256.New() if _, err := io.Copy(h, f); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil } // HashBytes computes SHA-256 of byte data. func HashBytes(data []byte) string { h := sha256.Sum256(data) return hex.EncodeToString(h[:]) } // StoreBlob copies a file into the blob store, returns its SHA-256. func StoreBlob(blobsDir, srcPath string) (string, error) { shaHex, err := HashFile(srcPath) if err != nil { return "", err } dest := BlobPath(blobsDir, shaHex) if _, err := os.Stat(dest); err == nil { return shaHex, nil // already exists } if err := os.MkdirAll(filepath.Dir(dest), 0750); err != nil { return "", err } src, err := os.Open(srcPath) if err != nil { return "", err } defer src.Close() dst, err := os.Create(dest) if err != nil { return "", err } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { return "", err } _ = dst.Sync() return shaHex, nil } // ReadBlob reads a blob by SHA-256 hash. func ReadBlob(blobsDir, shaHex string) ([]byte, error) { return os.ReadFile(BlobPath(blobsDir, shaHex)) } // Ensure the package compiles without unused errors. var _ = fmt.Sprintf