@@ -17,28 +17,93 @@ 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+ if event .Op & fsnotify .Write == fsnotify .Write {
80+ if option .notifier != nil {
81+ for _ , op := range option .notifier {
82+ op ()
83+ }
84+ }
85+ }
86+ case _ , ok := <- globalWatcher .Errors :
87+ if ! ok {
88+ continue
89+ }
90+ }
91+ }
92+ }()
93+
94+ if err := globalWatcher .Add (b .RootDirectory ); err != nil {
95+ panic (err )
96+ }
97+ })
98+
3999 return b
40100}
41101
102+ func (b LocalFilesystemBackend ) AddWatherPath (path string ) error {
103+ // TODO: to ensure that we should lock here ?
104+ return globalWatcher .Add (path )
105+ }
106+
42107// ListObjects lists all objects in root directory (depth 1)
43108func (b LocalFilesystemBackend ) ListObjects (prefix string ) ([]Object , error ) {
44109 var objects []Object
@@ -84,6 +149,7 @@ func (b LocalFilesystemBackend) PutObject(path string, content []byte) error {
84149 _ , err := os .Stat (folderPath )
85150 if err != nil {
86151 if os .IsNotExist (err ) {
152+ // NOTE: works for dynamic depth tenant
87153 err := os .MkdirAll (folderPath , 0774 )
88154 if err != nil {
89155 return err
@@ -95,17 +161,25 @@ func (b LocalFilesystemBackend) PutObject(path string, content []byte) error {
95161 if err != nil {
96162 return err
97163 }
164+ // also adds the fsnotify watcher path
165+ if err := b .AddWatherPath (folderPath ); err != nil {
166+ return err
167+ }
98168 } else {
99169 return err
100170 }
101171 }
102- err = ioutil .WriteFile (fullpath , content , 0644 )
103- return err
172+ if err = ioutil .WriteFile (fullpath , content , 0644 ); err != nil {
173+ return err
174+ }
175+ return nil
104176}
105177
106178// DeleteObject removes an object from root directory
107179func (b LocalFilesystemBackend ) DeleteObject (path string ) error {
108180 fullpath := pathutil .Join (b .RootDirectory , path )
109- err := os .Remove (fullpath )
110- return err
181+ if err := os .Remove (fullpath ); err != nil {
182+ return fmt .Errorf ("failed to delete object %s: %w" , path , err )
183+ }
184+ return nil
111185}
0 commit comments