diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index cef2c8e7..3180f0a8 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -99,8 +99,9 @@ type StoreGroup struct { type ImageGroup struct { Type string `long:"type" env:"TYPE" description:"type of storage" choice:"fs" choice:"bolt" choice:"mongo" default:"fs"` FS struct { - Path string `long:"path" env:"PATH" default:"./var/pictures" description:"images location"` - Partitons int `long:"partitions" env:"PARTITIONS" default:"100" description:"partitions (subdirs)"` + Path string `long:"path" env:"PATH" default:"./var/pictures" description:"images location"` + Staging string `long:"staging" env:"STAGING" default:"./var/pictures.staging" description:"staging location"` + Partitions int `long:"partitions" env:"PARTITIONS" default:"100" description:"partitions (subdirs)"` } `group:"fs" namespace:"fs" env-namespace:"FS"` Bolt struct { File string `long:"file" env:"FILE" default:"./var/pictures.db" description:"images bolt file location"` @@ -177,6 +178,7 @@ type serverApp struct { dataService *service.DataStore avatarStore avatar.Store notifyService *notify.Service + imageService image.Interface terminated chan struct{} } @@ -320,6 +322,7 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) { dataService: dataService, avatarStore: avatarStore, notifyService: notifyService, + imageService: pictStore, terminated: make(chan struct{}), }, nil } @@ -347,10 +350,14 @@ func (a *serverApp) run(ctx context.Context) error { a.notifyService.Close() log.Print("[INFO] shutdown completed") }() + a.activateBackup(ctx) // runs in goroutine for each site if a.Auth.Dev { go a.devAuth.Run(context.Background()) // dev oauth2 server on :8084 } + + go a.imageService.Cleanup(ctx) // pictures cleanup for staging images + a.restSrv.Run(a.Port) close(a.terminated) return nil @@ -433,7 +440,12 @@ func (s *ServerCommand) makePicturesStore() (image.Interface, error) { if err := makeDirs(s.Image.FS.Path); err != nil { return nil, err } - return &image.FileSystem{Location: s.Image.FS.Path, Partitions: s.Image.FS.Partitons, MaxSize: s.Image.MaxSize}, nil + return &image.FileSystem{ + Location: s.Image.FS.Path, + Staging: s.Image.FS.Staging, + Partitions: s.Image.FS.Partitions, + MaxSize: s.Image.MaxSize, + }, nil } return nil, errors.Errorf("unsupported pictures store type %s", s.Image.Type) } diff --git a/backend/app/store/image/image.go b/backend/app/store/image/image.go index 75b043cb..d9e58a98 100644 --- a/backend/app/store/image/image.go +++ b/backend/app/store/image/image.go @@ -24,7 +24,9 @@ import ( // Interface defines Save and Load methods type Interface interface { Save(fileName string, userID string, r io.Reader) (id string, err error) // get name and reader and returns ID of stored image + Commit(id string) error // move image from staging to permanent Load(id string) (io.ReadCloser, int64, error) // load image by ID. Caller has to close the reader. + Cleanup(ctx context.Context) // run removal loop for old images on staging } // FileSystem provides image Interface for local files. Saves and loads files from Location, restricts max size @@ -122,6 +124,7 @@ func (f *FileSystem) Load(id string) (io.ReadCloser, int64, error) { // Cleanup runs periodic scan of staging and removes old files based on TTL func (f *FileSystem) Cleanup(ctx context.Context) { + log.Printf("[INFO] start pictures cleanup, staging ttl=%v", f.TTL) cleanup := func() { err := filepath.Walk(f.Staging, func(path string, info os.FileInfo, err error) error {