func (r *Reporter) ReadWalk(ctx context.Context, path string) (*WalkFile, error) {
	b, err := ReadFile(ctx, path)
	if err != nil {
		return nil, err
	}
	p := &fspb.Walk{}
	if err := proto.Unmarshal(b, p); err != nil {
		return nil, err
	}
	fp := r.fingerprint(b)
	if r.Verbose {
		fmt.Printf("Loaded file %q with fingerprint: %s(%s)\n", path, fp.Method, fp.Value)
	}
	return &WalkFile{Path: path, Walk: p, Fingerprint: fp}, nil
}
ReadWalk reads a file as marshaled proto in fspb.Walk format.
package fswalker
import (
	"context"
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"log"
	"path"
	"sort"
	"strings"
	"time"
	"github.com/google/fswalker/internal/metrics"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"github.com/google/go-cmp/cmp"
	tspb "github.com/golang/protobuf/ptypes/timestamp"
	fspb "github.com/google/fswalker/proto/fswalker"
)
const (
	timeReportFormat = "2006-01-02 15:04:05 MST"
)
type WalkFile struct {
	Path        string
	Walk        *fspb.Walk
	Fingerprint *fspb.Fingerprint
}
type Report struct {
	Added      []ActionData
	Deleted    []ActionData
	Modified   []ActionData
	Errors     []ActionData
	Counter    *metrics.Counter
	WalkBefore *fspb.Walk
	WalkAfter  *fspb.Walk
}
func (r *Report) Empty() bool {
	return len(r.Added)+len(r.Deleted)+len(r.Modified)+len(r.Errors) == 0
}
type ActionData struct {
	Before *fspb.File
	After  *fspb.File
	Diff   string
	Err    error
}
func ReporterFromConfigFile(ctx context.Context, path string, verbose bool) (*Reporter, error) {
	config := &fspb.ReportConfig{}
	if err := readTextProto(ctx, path, config); err != nil {
		return nil, err
	}
	return &Reporter{
		config:     config,
		configPath: path,
		Verbose:    verbose,
	}, nil
}
type Reporter struct {
	config     *fspb.ReportConfig
	configPath string
	Verbose bool
}
func (r *Reporter) verifyFingerprint(goodFp *fspb.Fingerprint, checkFp *fspb.Fingerprint) error {
	if checkFp.Method != goodFp.Method {
		return fmt.Errorf("fingerprint method %q doesn't match %q", checkFp.Method, goodFp.Method)
	}
	if goodFp.Method == fspb.Fingerprint_UNKNOWN {
		return errors.New("undefined fingerprint method")
	}
	if goodFp.Value == "" {
		return errors.New("empty fingerprint value")
	}
	if checkFp.Value != goodFp.Value {
		return fmt.Errorf("fingerprint %q doesn't match %q", checkFp.Value, goodFp.Value)
	}
	return nil
}
func (r *Reporter) fingerprint(b []byte) *fspb.Fingerprint {
	v := fmt.Sprintf("%x", sha256.Sum256(b))
	return &fspb.Fingerprint{
		Method: fspb.Fingerprint_SHA256,
		Value:  v,
	}
}