113 lines
3.1 KiB
Go
113 lines
3.1 KiB
Go
package publish
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type Attempt struct {
|
|
RecordPath string
|
|
Evidence Evidence
|
|
}
|
|
type AttemptsResult struct {
|
|
RecordDir string
|
|
Attempts []Attempt
|
|
Diagnostics []*RecoveryError
|
|
}
|
|
|
|
func ListAttempts(ctx context.Context, repositoryDir, override string) (AttemptsResult, error) {
|
|
return listAttempts(ctx, repositoryDir, override, recordPaths)
|
|
}
|
|
|
|
func listAttempts(ctx context.Context, repositoryDir, override string, enumerate func(context.Context, string) ([]string, error)) (AttemptsResult, error) {
|
|
if repositoryDir == "" {
|
|
repositoryDir = "."
|
|
}
|
|
result := AttemptsResult{Attempts: []Attempt{}, Diagnostics: []*RecoveryError{}}
|
|
if err := ctx.Err(); err != nil {
|
|
return result, recoveryFailure(ctx, "storage_enumeration", err)
|
|
}
|
|
var directory string
|
|
{
|
|
resolved, err := recordDirectoryFor(ctx, repositoryDir, override)
|
|
if err != nil {
|
|
return result, recoveryFailure(ctx, "storage_enumeration", err)
|
|
}
|
|
directory = resolved
|
|
}
|
|
result.RecordDir = directory
|
|
if err := ctx.Err(); err != nil {
|
|
return result, &RecoveryError{Kind: "canceled", cause: err}
|
|
}
|
|
paths, enumerationErr := enumerate(ctx, directory)
|
|
sort.Strings(paths)
|
|
for _, path := range paths {
|
|
if err := ctx.Err(); err != nil {
|
|
return result, &RecoveryError{Kind: "canceled", cause: err}
|
|
}
|
|
absolute, err := filepath.Abs(path)
|
|
if err != nil {
|
|
result.Diagnostics = append(result.Diagnostics, &RecoveryError{Kind: "record_read", RecordPath: path, cause: err})
|
|
continue
|
|
}
|
|
evidence, err := LoadEvidence(absolute)
|
|
if err != nil {
|
|
var recordError *RecoveryError
|
|
if errors.As(err, &recordError) {
|
|
result.Diagnostics = append(result.Diagnostics, recordError)
|
|
}
|
|
continue
|
|
}
|
|
result.Attempts = append(result.Attempts, Attempt{RecordPath: absolute, Evidence: evidence})
|
|
}
|
|
if enumerationErr != nil {
|
|
return result, recoveryFailure(ctx, "storage_enumeration", fmt.Errorf("enumerate records in %q: %w", directory, enumerationErr))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// recordPaths filters unsorted batches, retaining only selected record names.
|
|
func recordPaths(ctx context.Context, directory string) ([]string, error) {
|
|
file, err := os.Open(directory)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
return readRecordPaths(ctx, directory, file.ReadDir)
|
|
}
|
|
|
|
func readRecordPaths(ctx context.Context, directory string, read func(int) ([]os.DirEntry, error)) ([]string, error) {
|
|
var paths []string
|
|
for {
|
|
if err := ctx.Err(); err != nil {
|
|
return paths, err
|
|
}
|
|
entries, err := read(128)
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".json") {
|
|
paths = append(paths, filepath.Join(directory, entry.Name()))
|
|
}
|
|
}
|
|
if errors.Is(err, io.EOF) {
|
|
return paths, nil
|
|
}
|
|
if err != nil {
|
|
return paths, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func recordDirectoryFor(ctx context.Context, repositoryDir, override string) (string, error) {
|
|
if filepath.IsAbs(override) {
|
|
return filepath.Clean(override), nil
|
|
}
|
|
directory, _, err := resolveRecordDir(ctx, repositoryDir, override)
|
|
return directory, err
|
|
}
|