github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/pkg/nix/cache.go (about)

     1  package nix
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/benchkram/errz"
    12  
    13  	"github.com/benchkram/bob/bob/global"
    14  	"github.com/benchkram/bob/pkg/file"
    15  	"github.com/benchkram/bob/pkg/filehash"
    16  )
    17  
    18  type Cache struct {
    19  	db   map[string]string
    20  	f    *os.File
    21  	path string
    22  }
    23  
    24  type CacheOption func(f *Cache)
    25  
    26  // WithPath adds a custom file path which is used
    27  // to store cache content on the filesystem.
    28  func WithPath(path string) CacheOption {
    29  	return func(n *Cache) {
    30  		n.path = path
    31  	}
    32  }
    33  
    34  // NewCacheStore initialize a Nix cache store inside dir
    35  func NewCacheStore(opts ...CacheOption) (_ *Cache, err error) {
    36  	defer errz.Recover(&err)
    37  
    38  	c := Cache{
    39  		db:   make(map[string]string),
    40  		path: global.BobNixCacheFile,
    41  	}
    42  
    43  	for _, opt := range opts {
    44  		if opt == nil {
    45  			continue
    46  		}
    47  		opt(&c)
    48  	}
    49  
    50  	f, err := os.OpenFile(c.path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
    51  	errz.Fatal(err)
    52  	c.f = f
    53  
    54  	scanner := bufio.NewScanner(f)
    55  	for scanner.Scan() {
    56  		pair := strings.SplitN(scanner.Text(), ":", 2)
    57  
    58  		c.db[pair[0]] = pair[1]
    59  	}
    60  
    61  	if err := scanner.Err(); err != nil {
    62  		errz.Fatal(err)
    63  	}
    64  
    65  	return &c, nil
    66  }
    67  
    68  // Get value from cache by its key
    69  // Additionally also checks if path exists on the system
    70  func (c *Cache) Get(key string) (string, bool) {
    71  	path, ok := c.db[key]
    72  
    73  	// Assure path exists on the filesystem.
    74  	if ok && !file.Exists(path) {
    75  		return path, false
    76  	}
    77  
    78  	return path, ok
    79  }
    80  
    81  // Save dependency inside the cache with its corresponding store path
    82  func (c *Cache) Save(key string, storePath string) (err error) {
    83  	defer errz.Recover(&err)
    84  
    85  	if _, err := c.f.Write([]byte(fmt.Sprintf("%s:%s\n", key, storePath))); err != nil {
    86  		_ = c.f.Close() // ignore error; Write error takes precedence
    87  		errz.Fatal(err)
    88  	}
    89  	c.db[key] = storePath
    90  
    91  	return nil
    92  }
    93  
    94  // Close closes the file used in cache
    95  func (c *Cache) Close() error {
    96  	return c.f.Close()
    97  }
    98  
    99  func (c *Cache) Clean() (err error) {
   100  	defer errz.Recover(&err)
   101  	err = c.f.Truncate(0)
   102  	errz.Fatal(err)
   103  
   104  	c.db = make(map[string]string)
   105  	return nil
   106  }
   107  
   108  // GenerateKey generates key for the cache for a Dependency
   109  //
   110  // if it's a .nix file it will hash the nixpkgs + file contents
   111  // if it's a package name will hash the packageName:nixpkgs content
   112  func GenerateKey(dependency Dependency) (_ string, err error) {
   113  	defer errz.Recover(&err)
   114  	var h []byte
   115  
   116  	if strings.HasSuffix(dependency.Name, ".nix") {
   117  		aggregatedHashes := bytes.NewBuffer([]byte{})
   118  		h, err = filehash.HashBytes(bytes.NewBufferString(dependency.Nixpkgs))
   119  		errz.Fatal(err)
   120  		aggregatedHashes.Write(h)
   121  
   122  		fileHash, err := filehash.Hash(dependency.Name)
   123  		errz.Fatal(err)
   124  		aggregatedHashes.Write(fileHash)
   125  
   126  		h, err = filehash.HashBytes(aggregatedHashes)
   127  		errz.Fatal(err)
   128  	} else {
   129  		toHash := fmt.Sprintf("%s:%s", dependency.Name, dependency.Nixpkgs)
   130  		h, err = filehash.HashBytes(bytes.NewBufferString(toHash))
   131  		errz.Fatal(err)
   132  	}
   133  
   134  	return hex.EncodeToString(h), nil
   135  }