@@ -17,28 +17,97 @@ limitations under the License.
1717package storage
1818
1919import (
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
2832type 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)
43112func (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
107183func (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