Skip to content

Commit cbf1958

Browse files
committed
pkg/store/chunkstore: add FilesystemStore
1 parent 09bdd35 commit cbf1958

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed

pkg/store/chunkstore/filesystem.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package chunkstore
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"io/ioutil"
10+
"os"
11+
"path/filepath"
12+
"sync"
13+
14+
"github.com/nix-community/go-nix/pkg/store"
15+
)
16+
17+
var _ store.ChunkStore = &FilesystemStore{}
18+
19+
func NewFilesystemStore(hashName string, baseDirectory string) (*FilesystemStore, error) {
20+
err := os.MkdirAll(baseDirectory, os.ModePerm)
21+
if err != nil {
22+
return nil, fmt.Errorf("error mkdir'ing base directory: %w", err)
23+
}
24+
25+
hasherPool, err := store.NewHasherPool(hashName)
26+
if err != nil {
27+
return nil, fmt.Errorf("unable to create new hasher pool for %v: %w", hashName, err)
28+
}
29+
30+
return &FilesystemStore{
31+
baseDirectory: baseDirectory,
32+
hasherPool: *hasherPool,
33+
}, nil
34+
}
35+
36+
// TODO: generalize on io/fs? or rclone?
37+
38+
type FilesystemStore struct {
39+
baseDirectory string
40+
hasherPool sync.Pool
41+
// TODO: allow compression. Probably default to zstd.
42+
}
43+
44+
// chunkPath calculates the path on the filesystem to the chunk
45+
// identified by id.
46+
func (fs *FilesystemStore) chunkPath(id store.ChunkIdentifier) string {
47+
encodedID := hex.EncodeToString(id)
48+
return filepath.Join(fs.baseDirectory, encodedID[:4], encodedID+".chunk")
49+
}
50+
51+
func (fs *FilesystemStore) Get(
52+
ctx context.Context,
53+
id store.ChunkIdentifier,
54+
) ([]byte, error) {
55+
p := fs.chunkPath(id)
56+
57+
f, err := os.Open(p)
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
defer f.Close()
63+
64+
contents, err := io.ReadAll(f)
65+
if err != nil {
66+
return nil, fmt.Errorf("error reading file contents from %v: %w", p, err)
67+
}
68+
// TODO: configurable content validation?
69+
70+
return contents, nil
71+
}
72+
73+
func (fs *FilesystemStore) Has(
74+
ctx context.Context,
75+
id store.ChunkIdentifier,
76+
) (bool, error) {
77+
p := fs.chunkPath(id)
78+
79+
_, err := os.Stat(p)
80+
if err != nil {
81+
if errors.Is(err, io.EOF) {
82+
return false, nil
83+
}
84+
return false, fmt.Errorf("error stat()ing %v: %w", p, err)
85+
}
86+
87+
return true, nil
88+
}
89+
90+
func (fs *FilesystemStore) Put(
91+
ctx context.Context,
92+
data []byte,
93+
) (store.ChunkIdentifier, error) {
94+
// get a hasher
95+
hasher := fs.hasherPool.Get().(*store.Hasher)
96+
97+
// create a tempfile (in the same directory).
98+
// We write to it, then move it to where we want it to be
99+
// this is to ensure an atomic write/replacement.
100+
tmpFile, err := ioutil.TempFile(fs.baseDirectory, "")
101+
if err != nil {
102+
return nil, fmt.Errorf("error creating temporary file: %w", err)
103+
}
104+
105+
defer tmpFile.Close()
106+
defer os.Remove(tmpFile.Name())
107+
108+
w := io.MultiWriter(hasher, tmpFile)
109+
110+
_, err = w.Write(data)
111+
if err != nil {
112+
return nil, fmt.Errorf("error writing data: %w", err)
113+
}
114+
115+
id, err := hasher.Sum()
116+
if err != nil {
117+
return nil, fmt.Errorf("error calculating multihash: %w", err)
118+
}
119+
120+
// close tmpFile for writing, everything written
121+
err = tmpFile.Close()
122+
if err != nil {
123+
return nil, fmt.Errorf("error closing temporary file: %w", err)
124+
}
125+
126+
// calculate the final path to store the chunk at
127+
p := fs.chunkPath(id)
128+
129+
// create parent directories if needed
130+
err = os.MkdirAll(filepath.Dir(p), os.ModePerm)
131+
if err != nil {
132+
return nil, fmt.Errorf("unable to mkdir'ig parent directory for %v: %w", p, err)
133+
}
134+
135+
// move chunk at the location
136+
err = os.Rename(tmpFile.Name(), p)
137+
if err != nil {
138+
return nil, fmt.Errorf("error moving temporary file to it's final location (%v): %w", p, err)
139+
}
140+
141+
return id, nil
142+
}
143+
144+
// Close closes the store.
145+
func (fs *FilesystemStore) Close() error {
146+
return nil
147+
}

0 commit comments

Comments
 (0)