diff --git a/go.mod b/go.mod index a7b347f2..d257ef23 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/thoas/go-funk v0.9.3 github.com/thoas/stats v0.0.0-20160726120248-152b5d051953 github.com/ulule/gokvstores v0.1.1-0.20221229151109-3bd12fb72ebe - github.com/ulule/gostorages v0.2.5-0.20230314124119-11134a4bce61 + github.com/ulule/gostorages v0.2.5-0.20230920134537-c63293fd790c github.com/urfave/cli v1.22.10 golang.org/x/image v0.6.0 google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index 1cb5fc8c..6860aff8 100644 --- a/go.sum +++ b/go.sum @@ -441,6 +441,14 @@ github.com/ulule/gokvstores v0.1.1-0.20221229151109-3bd12fb72ebe h1:ayWYvm5FWr78 github.com/ulule/gokvstores v0.1.1-0.20221229151109-3bd12fb72ebe/go.mod h1:2buRSW9ZL73BFOkRwBSOVcPJG/JQxcbZPs8fo0TD5X0= github.com/ulule/gostorages v0.2.5-0.20230314124119-11134a4bce61 h1:Twadj4iaibm+nUX5PvElLeq5YsechTLpITZb/p27ywI= github.com/ulule/gostorages v0.2.5-0.20230314124119-11134a4bce61/go.mod h1:7yGiGHJ9mE+He6sB42cJaRE4zcselRo4g2GBd8NGEMU= +github.com/ulule/gostorages v0.2.5-0.20230707071807-ba19d4693f53 h1:uzC+DOzybak6pNOK7u0UdTomOEu2wPuCxkLad9WNs44= +github.com/ulule/gostorages v0.2.5-0.20230707071807-ba19d4693f53/go.mod h1:7yGiGHJ9mE+He6sB42cJaRE4zcselRo4g2GBd8NGEMU= +github.com/ulule/gostorages v0.2.5-0.20230707072626-d429b21ca9b4 h1:PTBZrQv/VKCYBZQdbBj5ug+wvJZ+HuYLjAhzzu5PnB4= +github.com/ulule/gostorages v0.2.5-0.20230707072626-d429b21ca9b4/go.mod h1:7yGiGHJ9mE+He6sB42cJaRE4zcselRo4g2GBd8NGEMU= +github.com/ulule/gostorages v0.2.5-0.20230710095702-bfbe5260f605 h1:PYD0HGywjlJ+WxFivejD2zyVRD0rRclPmCHx+HdBbXQ= +github.com/ulule/gostorages v0.2.5-0.20230710095702-bfbe5260f605/go.mod h1:7yGiGHJ9mE+He6sB42cJaRE4zcselRo4g2GBd8NGEMU= +github.com/ulule/gostorages v0.2.5-0.20230920134537-c63293fd790c h1:bNM3RCu+JUVpPW3yZ7aWb/CIpW5DO8z0PGuqAquCnTE= +github.com/ulule/gostorages v0.2.5-0.20230920134537-c63293fd790c/go.mod h1:nMhvJt6g5Ulmp3Y1M159+xvsaF3EPwdWdvBnjYAWK3o= github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/image/factory.go b/image/factory.go index a8f0be69..973f2d3f 100644 --- a/image/factory.go +++ b/image/factory.go @@ -41,16 +41,11 @@ func FromURL(ctx context.Context, u *url.URL, userAgent string) (*ImageFile, err } // FromStorage retrieves an ImageFile from storage -func FromStorage(ctx context.Context, storage storagepkg.Storage, filepath string) (*ImageFile, error) { +func FromStorage(ctx context.Context, storage *storagepkg.Storage, filepath string) (*ImageFile, error) { var file *ImageFile var err error - f, err := storage.Open(ctx, filepath) - if err != nil { - return nil, errors.WithStack(err) - } - - stat, err := storage.Stat(ctx, filepath) + f, stat, err := storage.OpenWithStat(ctx, filepath) if err != nil { return nil, errors.WithStack(err) } diff --git a/image/file.go b/image/file.go index 9297c135..1e5822e0 100644 --- a/image/file.go +++ b/image/file.go @@ -17,7 +17,7 @@ type ImageFile struct { Key string Processed []byte Source []byte - Storage storage.Storage + Storage *storage.Storage } func (i *ImageFile) URL() string { diff --git a/middleware/metrics.go b/middleware/metrics.go index 1f034a76..36e1c3a9 100644 --- a/middleware/metrics.go +++ b/middleware/metrics.go @@ -2,11 +2,12 @@ package middleware import ( "fmt" + "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" ) -func MetricsMiddlewares(c *gin.Context) { +func Metrics(c *gin.Context) { c.Next() customMetrics.histogram.WithLabelValues( c.Request.Method, diff --git a/server/middleware.go b/middleware/recover.go similarity index 88% rename from server/middleware.go rename to middleware/recover.go index b166bdae..4e5480e3 100644 --- a/server/middleware.go +++ b/middleware/recover.go @@ -1,4 +1,4 @@ -package server +package middleware import ( "net/http" @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" ) -func recoverMiddleware(c *gin.Context) { +func Recover(c *gin.Context) { defer func() { // We must abort if there has been a recover, otherwise the // remaining handlers would be called. If everything went fine, diff --git a/picfit.go b/picfit.go index a0e6a430..5f52e136 100644 --- a/picfit.go +++ b/picfit.go @@ -2,9 +2,10 @@ package picfit import ( "context" - "github.com/thoas/picfit/constants" "log/slog" + "github.com/thoas/picfit/constants" + "github.com/thoas/picfit/config" "github.com/thoas/picfit/engine" "github.com/thoas/picfit/logger" @@ -39,9 +40,9 @@ func NewProcessor(ctx context.Context, cfg *config.Config) (*Processor, error) { Logger: log, config: cfg, - destinationStorage: *destinationStorage, + destinationStorage: destinationStorage, engine: e, - sourceStorage: *sourceStorage, + sourceStorage: sourceStorage, store: s, }, nil } diff --git a/processor.go b/processor.go index d3312dce..1ac3eaad 100644 --- a/processor.go +++ b/processor.go @@ -31,9 +31,9 @@ type Processor struct { Logger *slog.Logger config *config.Config - destinationStorage storage.Storage + destinationStorage *storage.Storage engine *engine.Engine - sourceStorage storage.Storage + sourceStorage *storage.Storage store store.Store } @@ -46,8 +46,9 @@ func (p *Processor) Upload(ctx context.Context, payload *payload.Multipart) (*im return nil, err } - if err := p.sourceStorage.Save(ctx, fh, payload.Data.Filename); err != nil { - return nil, errors.Wrapf(err, "unable to save data on storage as: %s", payload.Data.Filename) + filepath := payload.Data.Filename + if err := p.sourceStorage.Save(ctx, fh, filepath); err != nil { + return nil, errors.Wrapf(err, "unable to save data on storage as: %s", filepath) } if err := fh.Close(); err != nil { return nil, errors.WithStack(err) diff --git a/processor_test.go b/processor_test.go index 4150a365..109d49aa 100644 --- a/processor_test.go +++ b/processor_test.go @@ -211,7 +211,8 @@ func TestUploadHandler(t *testing.T) { assert.Equal(t, 200, res.Code) - assert.True(t, suite.Processor.FileExists(context.Background(), "avatar.png")) + exists := suite.Processor.FileExists(context.Background(), "avatar.png") + assert.True(t, exists) file, err := suite.Processor.OpenFile(context.Background(), "avatar.png") assert.NoError(t, err) @@ -237,7 +238,8 @@ func TestDeleteHandler(t *testing.T) { // copy 5 images to src storage for i := 0; i < 5; i++ { fn := fmt.Sprintf("image%d.jpg", i+1) - err = os.WriteFile(filepath.Join(tmpSrcStorage, fn), img, 0644) + filepath := filepath.Join(tmpSrcStorage, fn) + err = os.WriteFile(filepath, img, 0644) assert.Nil(t, err) } @@ -285,7 +287,7 @@ func TestDeleteHandler(t *testing.T) { res := httptest.NewRecorder() server.ServeHTTP(res, req) - assert.Equal(t, res.Code, 200) + assert.Equal(t, 200, res.Code) } checkDirCount(tmpDstStorage, 5, "after resize requests") @@ -299,7 +301,7 @@ func TestDeleteHandler(t *testing.T) { res := httptest.NewRecorder() server.ServeHTTP(res, req) - assert.Equal(t, res.Code, 200) + assert.Equal(t, 200, res.Code) } checkDirCount(tmpSrcStorage, 5, "after resize requests") @@ -312,7 +314,7 @@ func TestDeleteHandler(t *testing.T) { res := httptest.NewRecorder() server.ServeHTTP(res, req) - assert.Equal(t, res.Code, 200) + assert.Equal(t, 200, res.Code) checkDirCount(tmpSrcStorage, 4, "after 1st delete request") checkDirCount(tmpDstStorage, 2, "after 1st delete request") @@ -324,7 +326,7 @@ func TestDeleteHandler(t *testing.T) { res = httptest.NewRecorder() server.ServeHTTP(res, req) - assert.Equal(t, res.Code, 404) + assert.Equal(t, 404, res.Code) checkDirCount(tmpSrcStorage, 4, "after 2nd delete request") checkDirCount(tmpDstStorage, 2, "after 2nd delete request") @@ -339,7 +341,7 @@ func TestDeleteHandler(t *testing.T) { res = httptest.NewRecorder() server.ServeHTTP(res, req) - assert.Equal(t, res.Code, 200) + assert.Equal(t, 200, res.Code) checkDirCount(tmpSrcStorage, 3, "after 3rd delete request") checkDirCount(tmpDstStorage, 0, "after 3rd delete request") diff --git a/server/http.go b/server/http.go index cea687b7..00c404a0 100644 --- a/server/http.go +++ b/server/http.go @@ -65,11 +65,11 @@ func (s *HTTPServer) Init() error { if s.config.Debug { router.Use(gin.Recovery()) } else { - router.Use(recoverMiddleware) + router.Use(middleware.Recover) } router.Use(middleware.NewLogger(s.config, s.processor.Logger)) - router.Use(middleware.MetricsMiddlewares) + router.Use(middleware.Metrics) if s.config.Sentry != nil { if err := sentry.Init(sentry.ClientOptions{ diff --git a/storage/dummy.go b/storage/dummy.go index 1cd3740a..866b05e9 100644 --- a/storage/dummy.go +++ b/storage/dummy.go @@ -25,3 +25,7 @@ func (s *DummyStorage) Open(ctx context.Context, filepath string) (io.ReadCloser func (s DummyStorage) Stat(ctx context.Context, path string) (*gostorages.Stat, error) { return &gostorages.Stat{}, nil } + +func (s DummyStorage) OpenWithStat(ctx context.Context, path string) (io.ReadCloser, *gostorages.Stat, error) { + return nil, &gostorages.Stat{}, nil +} diff --git a/storage/storage.go b/storage/storage.go index d39d4b1a..4e789da3 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -3,6 +3,7 @@ package storage import ( "context" "fmt" + "io" "log/slog" "path" "strings" @@ -27,8 +28,8 @@ const ( // Storage wraps gostorages.Storage. type Storage struct { - gostorages.Storage - cfg StorageConfig + storage gostorages.Storage + cfg StorageConfig } // URL returns the filepath prefixed with BaseURL from storage. @@ -43,13 +44,38 @@ func (s *Storage) URL(filepath string) string { // Path returns the filepath prefixed with Location from storage. func (s *Storage) Path(filepath string) string { + if _, ok := s.storage.(*fsstorage.Storage); ok { + return filepath + } + return path.Join(s.cfg.Location, filepath) } +func (s *Storage) Save(ctx context.Context, content io.Reader, path string) error { + filepath := s.Path(path) + return s.storage.Save(ctx, content, filepath) +} + +func (s *Storage) Stat(ctx context.Context, path string) (*gostorages.Stat, error) { + return s.storage.Stat(ctx, s.Path(path)) +} + +func (s *Storage) Open(ctx context.Context, path string) (io.ReadCloser, error) { + return s.storage.Open(ctx, s.Path(path)) +} + +func (s *Storage) OpenWithStat(ctx context.Context, path string) (io.ReadCloser, *gostorages.Stat, error) { + return s.storage.OpenWithStat(ctx, s.Path(path)) + +} +func (s *Storage) Delete(ctx context.Context, path string) error { + return s.storage.Delete(ctx, s.Path(path)) +} + // New return destination and source storages from config func New(ctx context.Context, log *slog.Logger, cfg *Config) (*Storage, *Storage, error) { if cfg == nil { - storage := &Storage{Storage: &DummyStorage{}} + storage := &Storage{storage: &DummyStorage{}} log.InfoContext(ctx, "Source storage configured", slog.String("type", "dummy")) @@ -78,10 +104,10 @@ func New(ctx context.Context, log *slog.Logger, cfg *Config) (*Storage, *Storage slog.String("type", cfg.Source.Type)) return &Storage{ - Storage: sourceStorage, + storage: sourceStorage, cfg: *cfg.Source, }, &Storage{ - Storage: sourceStorage, + storage: sourceStorage, cfg: *cfg.Source, }, nil } @@ -95,10 +121,10 @@ func New(ctx context.Context, log *slog.Logger, cfg *Config) (*Storage, *Storage slog.String("type", cfg.Destination.Type)) return &Storage{ - Storage: sourceStorage, + storage: sourceStorage, cfg: *cfg.Source, }, &Storage{ - Storage: destinationStorage, + storage: destinationStorage, cfg: *cfg.Destination, }, nil }