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 }