summaryrefslogtreecommitdiff
path: root/shelf/shelf.go
diff options
context:
space:
mode:
Diffstat (limited to 'shelf/shelf.go')
-rw-r--r--shelf/shelf.go134
1 files changed, 134 insertions, 0 deletions
diff --git a/shelf/shelf.go b/shelf/shelf.go
new file mode 100644
index 0000000..6df46fc
--- /dev/null
+++ b/shelf/shelf.go
@@ -0,0 +1,134 @@
+package shelf
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io"
+ "io/fs"
+ "path/filepath"
+ "runtime"
+ "slices"
+ "strings"
+ "sync"
+
+ "golang.org/x/sync/errgroup"
+)
+
+var Root = ".shelf"
+
+type Sum map[Item]string
+
+func Index(ignore []byte) (Sum, error) {
+ sum := make(Sum)
+
+ var rw sync.RWMutex
+
+ g, ctx := errgroup.WithContext(context.Background())
+
+ sem := make(chan struct{}, max(1, runtime.NumCPU()-1))
+
+ ign := newIgnore(ignore)
+
+ rootPath := filepath.Clean(Root)
+
+ walkErr := filepath.WalkDir(rootPath, func(abs string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return nil
+ }
+
+ rel, relErr := filepath.Rel(rootPath, abs)
+
+ if relErr != nil {
+ return err
+ }
+
+ if rel == "." {
+ return nil
+ }
+
+ if ign.Match(rel, d.IsDir()) {
+ if d.IsDir() {
+ return fs.SkipDir
+ }
+
+ return nil
+ }
+
+ if d.IsDir() || !d.Type().IsRegular() {
+ return nil
+ }
+
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case sem <- struct{}{}:
+ }
+
+ g.Go(func() error {
+ defer func() { <-sem }()
+
+ hash, hashErr := hashFile(abs)
+
+ if hashErr != nil {
+ return hashErr
+ }
+
+ rw.Lock()
+
+ sum[Item(rel)] = hash
+
+ rw.Unlock()
+
+ return nil
+ })
+
+ return nil
+ })
+
+ if err := g.Wait(); err != nil {
+ return nil, err
+ }
+
+ return sum, walkErr
+}
+
+func Serialize(s Sum, w io.Writer) error {
+ items := make([]Item, 0, len(s))
+
+ for item := range s {
+ items = append(items, item)
+ }
+
+ slices.SortFunc(items, func(a, b Item) int {
+ return strings.Compare(string(a), string(b))
+ })
+
+ for _, item := range items {
+ if _, err := fmt.Fprintf(w, "%s %s\n", s[item], item); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func Deserialize(r io.Reader) (Sum, error) {
+ sum := make(Sum)
+
+ s := bufio.NewScanner(r)
+
+ for s.Scan() {
+ line := s.Text()
+
+ parts := strings.SplitN(line, " ", 2)
+
+ if len(parts) != 2 {
+ return nil, fmt.Errorf("malformed line: %q", line)
+ }
+
+ sum[Item(parts[1])] = parts[0]
+ }
+
+ return sum, s.Err()
+}