diff --git a/go.mod b/go.mod index 308d52f3d..fa5c2ce3a 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/restic/calens v0.2.0 github.com/spf13/afero v1.2.2 // indirect github.com/spf13/viper v1.5.0 + github.com/stretchr/testify v1.4.0 go.opencensus.io v0.22.2 golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect diff --git a/go.sum b/go.sum index 774f1c0ca..c54ad9fda 100644 --- a/go.sum +++ b/go.sum @@ -455,7 +455,7 @@ github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukw github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= github.com/owncloud/ocis-pkg/v2 v2.2.1 h1:LK7WxHYugEFQ9NHTOz0EP8DRjbt51wXhyqruV03z6zI= github.com/owncloud/ocis-pkg/v2 v2.2.1/go.mod h1:MXv7QzsYsu4YWuyJxhq1kLLmJa/r5gbqHe1FXulMHaw= -github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +ithub.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= diff --git a/pkg/config/config.go b/pkg/config/config.go index 37286518a..7d1b1645b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -51,6 +51,11 @@ type WebDavSource struct { BaseURL string } +// FileSystemSource defines the available filesystem source configuration. +type FileSystemSource struct { + BasePath string +} + // Thumbnail defines the available thumbnail related configuration. type Thumbnail struct { Resolutions []string diff --git a/pkg/proto/v0/thumbnails.pb.micro_test.go b/pkg/proto/v0/thumbnails.pb.micro_test.go new file mode 100644 index 000000000..22dd00571 --- /dev/null +++ b/pkg/proto/v0/thumbnails.pb.micro_test.go @@ -0,0 +1,87 @@ +package proto_test + +import ( + "bytes" + "context" + "image" + "log" + "os" + "path/filepath" + "testing" + + "github.com/owncloud/ocis-pkg/v2/service/grpc" + "github.com/owncloud/ocis-thumbnails/pkg/config" + "github.com/owncloud/ocis-thumbnails/pkg/proto/v0" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/imgsource" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/storage" + "github.com/stretchr/testify/assert" + + svc "github.com/owncloud/ocis-thumbnails/pkg/service/v0" +) + +var service = grpc.Service{} + +func init() { + service = grpc.NewService( + grpc.Namespace("com.owncloud.api"), + grpc.Name("thumbnails"), + grpc.Address("localhost:9992"), + ) + + cfg := config.New() + cfg.Thumbnail.Resolutions = []string{"16x16", "32x32", "64x64", "128x128"} + + wd, _ := os.Getwd() + fsCfg := config.FileSystemSource{ + BasePath: filepath.Join(wd, "../../../testdata/"), + } + err := proto.RegisterThumbnailServiceHandler( + service.Server(), + svc.NewService( + svc.Config(cfg), + svc.ThumbnailStorage(storage.NewInMemoryStorage()), + svc.ThumbnailSource(imgsource.NewFileSystemSource(fsCfg)), + ), + ) + if err != nil { + log.Fatalf("could not register ThumbnailHandler: %v", err) + } + service.Server().Start() +} + +func TestGetThumbnailInvalidImage(t *testing.T) { + req := proto.GetRequest{ + Filepath: "invalid.png", + Filetype: proto.GetRequest_PNG, + Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4", + Height: 32, + Width: 32, + } + client := service.Client() + cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client) + _, err := cl.GetThumbnail(context.Background(), &req) + + assert.NotNil(t, err) +} + +func TestGetThumbnail(t *testing.T) { + req := proto.GetRequest{ + Filepath: "oc.png", + Filetype: proto.GetRequest_PNG, + Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4", + Height: 32, + Width: 32, + } + client := service.Client() + cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client) + rsp, err := cl.GetThumbnail(context.Background(), &req) + if err != nil { + log.Fatalf("error %s", err.Error()) + } + assert.NotEmpty(t, rsp.GetThumbnail()) + + img, _, _ := image.Decode(bytes.NewReader(rsp.GetThumbnail())) + assert.Equal(t, 32, img.Bounds().Size().X) + + assert.Equal(t, "image/png", rsp.GetMimetype()) +} diff --git a/pkg/proto/v0/thumbnails.pb_test.go b/pkg/proto/v0/thumbnails.pb_test.go new file mode 100644 index 000000000..56af7f298 --- /dev/null +++ b/pkg/proto/v0/thumbnails.pb_test.go @@ -0,0 +1,98 @@ +package proto_test + +import ( + "testing" + + "github.com/owncloud/ocis-thumbnails/pkg/proto/v0" + "github.com/stretchr/testify/assert" +) + +type TestRequest struct { + testDataName string + filepath string + filetype proto.GetRequest_FileType + etag string + width int32 + height int32 + authorization string + expected string +} + +type TestResponse struct { + testDataName string + img []byte + mimetype string + expected string +} + +func TestRequestString(t *testing.T) { + + var tests = []TestRequest{ + { + "ASCII", + "Foo.jpg", + proto.GetRequest_JPG, + "33a64df551425fcc55e4d42a148795d9f25f89d4", + 24, + 24, + "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK", + `filepath:"Foo.jpg" filetype:JPG etag:"33a64df551425fcc55e4d42a148795d9f25f89d4" width:24 height:24 authorization:"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK" `, + }, + { + "UTF", + "मिलन.jpg", + proto.GetRequest_JPG, + "33a64df551425fcc55e4d42a148795d9f25f89d4", + 24, + 24, + "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK", + `filepath:"\340\244\256\340\244\277\340\244\262\340\244\250.jpg" filetype:JPG etag:"33a64df551425fcc55e4d42a148795d9f25f89d4" width:24 height:24 authorization:"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK" `, + }, + { + "PNG", + "Foo.png", + proto.GetRequest_PNG, + "33a64df551425fcc55e4d42a148795d9f25f89d4", + 24, + 24, + "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK", + `filepath:"Foo.png" etag:"33a64df551425fcc55e4d42a148795d9f25f89d4" width:24 height:24 authorization:"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK" `, + }, + } + + for _, testCase := range tests { + t.Run(testCase.testDataName, func(t *testing.T) { + req := proto.GetRequest{ + Filepath: testCase.filepath, + Filetype: testCase.filetype, + Etag: testCase.etag, + Height: testCase.height, + Width: testCase.width, + Authorization: testCase.authorization, + } + assert.Equal(t, testCase.expected, req.String()) + }) + } +} + +func TestResponseString(t *testing.T) { + var tests = []TestResponse{ + { + "ASCII", + []byte("image data"), + "image/png", + `thumbnail:"image data" mimetype:"image/png" `, + }, + } + + for _, testCase := range tests { + t.Run(testCase.testDataName, func(t *testing.T) { + response := proto.GetResponse{ + Thumbnail: testCase.img, + Mimetype: testCase.mimetype, + } + + assert.Equal(t, testCase.expected, response.String()) + }) + } +} diff --git a/pkg/server/grpc/server.go b/pkg/server/grpc/server.go index 3a7de2c93..ed67be00b 100644 --- a/pkg/server/grpc/server.go +++ b/pkg/server/grpc/server.go @@ -4,6 +4,8 @@ import ( "github.com/owncloud/ocis-pkg/v2/service/grpc" "github.com/owncloud/ocis-thumbnails/pkg/proto/v0" svc "github.com/owncloud/ocis-thumbnails/pkg/service/v0" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/imgsource" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/storage" "github.com/owncloud/ocis-thumbnails/pkg/version" ) @@ -26,6 +28,13 @@ func NewService(opts ...Option) grpc.Service { thumbnail = svc.NewService( svc.Config(options.Config), svc.Logger(options.Logger), + svc.ThumbnailSource(imgsource.NewWebDavSource(options.Config.Thumbnail.WebDavSource)), + svc.ThumbnailStorage( + storage.NewFileSystemStorage( + options.Config.Thumbnail.FileSystemStorage, + options.Logger, + ), + ), ) thumbnail = svc.NewInstrument(thumbnail, options.Metrics) thumbnail = svc.NewLogging(thumbnail, options.Logger) diff --git a/pkg/service/v0/option.go b/pkg/service/v0/option.go index b8d33f7b7..c870c7096 100644 --- a/pkg/service/v0/option.go +++ b/pkg/service/v0/option.go @@ -3,8 +3,10 @@ package svc import ( "net/http" - "github.com/owncloud/ocis-thumbnails/pkg/config" "github.com/owncloud/ocis-pkg/v2/log" + "github.com/owncloud/ocis-thumbnails/pkg/config" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/imgsource" + "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/storage" ) // Option defines a single option function. @@ -12,9 +14,11 @@ type Option func(o *Options) // Options defines the available options for this package. type Options struct { - Logger log.Logger - Config *config.Config - Middleware []func(http.Handler) http.Handler + Logger log.Logger + Config *config.Config + Middleware []func(http.Handler) http.Handler + ThumbnailStorage storage.Storage + ImageSource imgsource.Source } // newOptions initializes the available default options. @@ -48,3 +52,17 @@ func Middleware(val ...func(http.Handler) http.Handler) Option { o.Middleware = val } } + +// ThumbnailStorage provides a function to set the thumbnail storage option. +func ThumbnailStorage(val storage.Storage) Option { + return func(o *Options) { + o.ThumbnailStorage = val + } +} + +// ThumbnailSource provides a function to set the image source option. +func ThumbnailSource(val imgsource.Source) Option { + return func(o *Options) { + o.ImageSource = val + } +} diff --git a/pkg/service/v0/service.go b/pkg/service/v0/service.go index 23208d19f..658a44a37 100644 --- a/pkg/service/v0/service.go +++ b/pkg/service/v0/service.go @@ -9,7 +9,6 @@ import ( "github.com/owncloud/ocis-thumbnails/pkg/thumbnail" "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/imgsource" "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/resolution" - "github.com/owncloud/ocis-thumbnails/pkg/thumbnail/storage" ) type contextKey string @@ -28,14 +27,11 @@ func NewService(opts ...Option) v0proto.ThumbnailServiceHandler { } svc := Thumbnail{ manager: thumbnail.NewSimpleManager( - storage.NewFileSystemStorage( - options.Config.Thumbnail.FileSystemStorage, - logger, - ), + options.ThumbnailStorage, logger, ), resolutions: resolutions, - source: imgsource.NewWebDavSource(options.Config.Thumbnail.WebDavSource), + source: options.ImageSource, logger: logger, } diff --git a/pkg/thumbnail/imgsource/filesystem.go b/pkg/thumbnail/imgsource/filesystem.go new file mode 100644 index 000000000..d84f59555 --- /dev/null +++ b/pkg/thumbnail/imgsource/filesystem.go @@ -0,0 +1,39 @@ +package imgsource + +import ( + "context" + "fmt" + "image" + "os" + "path/filepath" + + "github.com/owncloud/ocis-thumbnails/pkg/config" +) + +// NewFileSystemSource return a new FileSystem instance +func NewFileSystemSource(cfg config.FileSystemSource) FileSystem { + return FileSystem{ + basePath: cfg.BasePath, + } +} + +// FileSystem is an image source using the local file system +type FileSystem struct { + basePath string +} + +// Get retrieves an image from the filesystem. +func (s FileSystem) Get(ctx context.Context, file string) (image.Image, error) { + imgPath := filepath.Join(s.basePath, file) + f, err := os.Open(imgPath) + if err != nil { + return nil, fmt.Errorf("failed to load the file %s from %s error %s", file, imgPath, err.Error()) + } + + img, _, err := image.Decode(f) + if err != nil { + return nil, err + } + + return img, nil +} diff --git a/testdata/oc.png b/testdata/oc.png new file mode 100644 index 000000000..034ad0e30 Binary files /dev/null and b/testdata/oc.png differ