Skip to content

Commit fdf260a

Browse files
committed
feat: integrate fsnotify to watch local file changes
implements the helm/chartmuseum#545 Signed-off-by: scnace <[email protected]>
1 parent 994ca76 commit fdf260a

File tree

4 files changed

+96
-6
lines changed

4 files changed

+96
-6
lines changed

event.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package storage
2+
3+
// EventType represents the type of event kind
4+
type EventType uint8
5+
6+
const (
7+
EventPutObject EventType = iota + 1
8+
EventDeleteObject
9+
)

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/aliyun/aliyun-oss-go-sdk v2.2.0+incompatible
1010
github.com/aws/aws-sdk-go v1.43.16
1111
github.com/baidubce/bce-sdk-go v0.9.105
12+
github.com/fsnotify/fsnotify v1.5.1
1213
github.com/gophercloud/gophercloud v0.24.0
1314
github.com/oracle/oci-go-sdk v24.3.0+incompatible
1415
github.com/stretchr/testify v1.7.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
137137
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
138138
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
139139
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
140+
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
141+
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
140142
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
141143
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
142144
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

local.go

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,97 @@ limitations under the License.
1717
package storage
1818

1919
import (
20+
"fmt"
2021
"io/ioutil"
2122
"os"
23+
"sync"
2224

2325
pathutil "path"
2426
"path/filepath"
27+
28+
"github.com/fsnotify/fsnotify"
2529
)
2630

2731
// LocalFilesystemBackend is a storage backend for local filesystem storage
2832
type LocalFilesystemBackend struct {
2933
RootDirectory string
3034
}
3135

36+
type NewLocalFilesystemBackendOption struct {
37+
notifier map[EventType]func()
38+
}
39+
40+
var watcherOnce sync.Once
41+
var globalWatcher *fsnotify.Watcher
42+
43+
func WithEventNotifier(e EventType, fn func()) func(*NewLocalFilesystemBackendOption) {
44+
return func(option *NewLocalFilesystemBackendOption) {
45+
if option.notifier == nil {
46+
option.notifier = make(map[EventType]func())
47+
}
48+
option.notifier[e] = fn
49+
}
50+
}
51+
3252
// NewLocalFilesystemBackend creates a new instance of LocalFilesystemBackend
33-
func NewLocalFilesystemBackend(rootDirectory string) *LocalFilesystemBackend {
53+
func NewLocalFilesystemBackend(rootDirectory string, opts ...func(*NewLocalFilesystemBackendOption)) *LocalFilesystemBackend {
54+
var option NewLocalFilesystemBackendOption
55+
for _, opt := range opts {
56+
opt(&option)
57+
}
3458
absPath, err := filepath.Abs(rootDirectory)
3559
if err != nil {
3660
panic(err)
3761
}
38-
b := &LocalFilesystemBackend{RootDirectory: absPath}
62+
b := &LocalFilesystemBackend{
63+
RootDirectory: absPath,
64+
}
65+
66+
watcherOnce.Do(func() {
67+
globalWatcher, err = fsnotify.NewWatcher()
68+
if err != nil {
69+
panic(err)
70+
}
71+
// since it is a longTerm watcher , we do not need to close it
72+
go func() {
73+
for {
74+
select {
75+
case event, ok := <-globalWatcher.Events:
76+
if !ok {
77+
continue
78+
}
79+
80+
switch event.Op {
81+
case fsnotify.Write, fsnotify.Create:
82+
if fn, ok := option.notifier[EventPutObject]; ok {
83+
fn()
84+
}
85+
case fsnotify.Remove:
86+
if fn, ok := option.notifier[EventDeleteObject]; ok {
87+
fn()
88+
}
89+
}
90+
case _, ok := <-globalWatcher.Errors:
91+
if !ok {
92+
continue
93+
}
94+
}
95+
}
96+
}()
97+
98+
if err := globalWatcher.Add(b.RootDirectory); err != nil {
99+
panic(err)
100+
}
101+
})
102+
39103
return b
40104
}
41105

106+
func (b LocalFilesystemBackend) AddWatcherPath(path string) error {
107+
// TODO: to ensure that we should lock here ?
108+
return globalWatcher.Add(path)
109+
}
110+
42111
// ListObjects lists all objects in root directory (depth 1)
43112
func (b LocalFilesystemBackend) ListObjects(prefix string) ([]Object, error) {
44113
var objects []Object
@@ -84,6 +153,7 @@ func (b LocalFilesystemBackend) PutObject(path string, content []byte) error {
84153
_, err := os.Stat(folderPath)
85154
if err != nil {
86155
if os.IsNotExist(err) {
156+
// NOTE: works for dynamic depth tenant
87157
err := os.MkdirAll(folderPath, 0774)
88158
if err != nil {
89159
return err
@@ -95,17 +165,25 @@ func (b LocalFilesystemBackend) PutObject(path string, content []byte) error {
95165
if err != nil {
96166
return err
97167
}
168+
// also adds the fsnotify watcher path
169+
if err := b.AddWatherPath(folderPath); err != nil {
170+
return err
171+
}
98172
} else {
99173
return err
100174
}
101175
}
102-
err = ioutil.WriteFile(fullpath, content, 0644)
103-
return err
176+
if err = ioutil.WriteFile(fullpath, content, 0644); err != nil {
177+
return err
178+
}
179+
return nil
104180
}
105181

106182
// DeleteObject removes an object from root directory
107183
func (b LocalFilesystemBackend) DeleteObject(path string) error {
108184
fullpath := pathutil.Join(b.RootDirectory, path)
109-
err := os.Remove(fullpath)
110-
return err
185+
if err := os.Remove(fullpath); err != nil {
186+
return fmt.Errorf("failed to delete object %s: %w", path, err)
187+
}
188+
return nil
111189
}

0 commit comments

Comments
 (0)