From c46a5598d4c301ca4cd2044d03bc376628afd97a Mon Sep 17 00:00:00 2001 From: David Christofas Date: Thu, 26 Nov 2020 16:36:51 +0100 Subject: [PATCH] change image library and refactor thumbnails service --- .../unreleased/change-thumbnails-library.md | 6 + ocis/go.sum | 17 +-- thumbnails/go.mod | 3 + thumbnails/go.sum | 1 + thumbnails/pkg/service/v0/service.go | 27 ++-- .../pkg/thumbnail/imgsource/filesystem.go | 6 +- .../pkg/thumbnail/imgsource/imgsource.go | 11 +- thumbnails/pkg/thumbnail/imgsource/webdav.go | 11 +- .../pkg/thumbnail/resolution/resolution.go | 37 ----- .../thumbnail/resolution/resolution_test.go | 40 ----- .../pkg/thumbnail/resolution/resolutions.go | 74 ---------- .../thumbnail/resolution/resolutions_test.go | 111 -------------- thumbnails/pkg/thumbnail/resolutions.go | 110 ++++++++++++++ thumbnails/pkg/thumbnail/resolutions_test.go | 139 ++++++++++++++++++ .../pkg/thumbnail/storage/filesystem.go | 3 +- thumbnails/pkg/thumbnail/storage/storage.go | 6 +- .../thumbnail/{thumbnails.go => thumbnail.go} | 27 ++-- thumbnails/pkg/thumbnail/thumbnail_test.go | 47 ++++++ 18 files changed, 355 insertions(+), 321 deletions(-) create mode 100644 changelog/unreleased/change-thumbnails-library.md delete mode 100644 thumbnails/pkg/thumbnail/resolution/resolution.go delete mode 100644 thumbnails/pkg/thumbnail/resolution/resolution_test.go delete mode 100644 thumbnails/pkg/thumbnail/resolution/resolutions.go delete mode 100644 thumbnails/pkg/thumbnail/resolution/resolutions_test.go create mode 100644 thumbnails/pkg/thumbnail/resolutions.go create mode 100644 thumbnails/pkg/thumbnail/resolutions_test.go rename thumbnails/pkg/thumbnail/{thumbnails.go => thumbnail.go} (71%) create mode 100644 thumbnails/pkg/thumbnail/thumbnail_test.go diff --git a/changelog/unreleased/change-thumbnails-library.md b/changelog/unreleased/change-thumbnails-library.md new file mode 100644 index 00000000000..35c06b57d09 --- /dev/null +++ b/changelog/unreleased/change-thumbnails-library.md @@ -0,0 +1,6 @@ +Change: replace the library which scales the images + +The library went out of support. +Also did some refactoring of the thumbnails service code. + +https://github.com/owncloud/ocis/pull/910 diff --git a/ocis/go.sum b/ocis/go.sum index c08831cf350..32d04f27c9f 100644 --- a/ocis/go.sum +++ b/ocis/go.sum @@ -160,8 +160,6 @@ github.com/aws/aws-sdk-go v1.23.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.31/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.28.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.33.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.35.9 h1:b1HiUpdkFLJyoOQ7zas36YHzjNHH0ivHx/G5lWBeg+U= -github.com/aws/aws-sdk-go v1.35.9/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.35.23 h1:SCP0d0XvyJTDmfnHEQPvBaYi3kea1VNUo7uQmkVgFts= github.com/aws/aws-sdk-go v1.35.23/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-xray-sdk-go v0.9.4/go.mod h1:XtMKdBQfpVut+tJEwI7+dJFRxxRdxHDyVNp2tHXRq04= @@ -217,7 +215,6 @@ github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw= github.com/caddyserver/certmagic v0.10.6 h1:sCya6FmfaN74oZE46kqfaFOVoROD/mF36rTQfjN7TZc= github.com/caddyserver/certmagic v0.10.6/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ= -github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -233,7 +230,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cheggaaa/pb v1.0.28/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= @@ -295,7 +291,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da h1:WXnT88cFG2davqSFqvaFfzkSMC0lqh/8/rKZ+z7tYvI= github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= @@ -305,20 +300,11 @@ github.com/cs3org/cato v0.0.0-20200626150132-28a40e643719/go.mod h1:XJEZ3/EQuI3B github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20191128165347-19746c015c83/go.mod h1:IsVGyZrOLUQD48JIhlM/xb3Vz6He5o2+W0ZTfUGY+IU= github.com/cs3org/go-cs3apis v0.0.0-20200730121022-c4f3d4f7ddfd/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00 h1:LVl25JaflluOchVvaHWtoCynm5OaM+VNai0IYkcCSe0= github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/go-cs3apis v0.0.0-20201118090759-87929f5bae21 h1:mZpylrgnCgSeaZ5EznvHIPIKuaQHMHZDi2wkJtk4M8Y= github.com/cs3org/go-cs3apis v0.0.0-20201118090759-87929f5bae21/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cs3org/reva v0.0.2-0.20200115110931-4c7513415ec5/go.mod h1:Hk3eCcdhtv4eIhKvRK736fQuOyS1HuHnUcz0Dq6NK1A= github.com/cs3org/reva v1.1.0/go.mod h1:fBzTrNuAKdQ62ybjpdu8nyhBin90/3/3s6DGQDCdBp4= -github.com/cs3org/reva v1.3.1-0.20201023144216-cdb3d6688da5 h1:nkmk9ywGKpJthWeMYGBiXh4DD6mTCOZLRfGDjp9rsKg= -github.com/cs3org/reva v1.3.1-0.20201023144216-cdb3d6688da5/go.mod h1:V50GXMiT524bvxACFkrkZqBzK4BoXLSprxcPshBlSOY= -github.com/cs3org/reva v1.3.1-0.20201112131316-1c425035c8a2 h1:vBgCFcQMxcu7wDPo44Onw3ZXXZc3DrARz6V8rECZjVs= -github.com/cs3org/reva v1.3.1-0.20201112131316-1c425035c8a2/go.mod h1:oqkkfe0g/dvrFFrmwd/VVfmrxhfswHp7+IB2PNeADSE= -github.com/cs3org/reva v1.4.1-0.20201120104232-f5afafc04c3b/go.mod h1:oqkkfe0g/dvrFFrmwd/VVfmrxhfswHp7+IB2PNeADSE= -github.com/cs3org/reva v1.4.1-0.20201123062044-b2c4af4e897d/go.mod h1:MTBlfobTE8W2hgXQ9+r+75jpJa1TxD04IZm5TpS9H48= -github.com/cs3org/reva v1.4.1-0.20201125144025-57da0c27434c h1:x7HkQqFGA+7FJUfyDO9tM2gF5i540PQePx+1kGUUYgA= -github.com/cs3org/reva v1.4.1-0.20201125144025-57da0c27434c/go.mod h1:MTBlfobTE8W2hgXQ9+r+75jpJa1TxD04IZm5TpS9H48= github.com/cs3org/reva v1.4.1-0.20201125172625-a5ab834a565d h1:Sr6ZWGjTds5cWDei3mJev2+RPJ0iejKnVrYklr5mO+M= github.com/cs3org/reva v1.4.1-0.20201125172625-a5ab834a565d/go.mod h1:MTBlfobTE8W2hgXQ9+r+75jpJa1TxD04IZm5TpS9H48= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= @@ -1119,7 +1105,6 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS github.com/nats-io/stan.go v0.5.0/go.mod h1:dYqB+vMN3C2F9pT1FRQpg9eHbjPj6mP0yYuyBNuXHZE= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/netdata/go-orchestrator v0.0.0-20190905093727-c793edba0e8f/go.mod h1:ECF8anFVCt/TfTIWVPgPrNaYJXtAtpAOF62ugDbw41A= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -1601,6 +1586,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1782,7 +1768,6 @@ golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/thumbnails/go.mod b/thumbnails/go.mod index 21bfb4d653d..a5bea1c04cd 100644 --- a/thumbnails/go.mod +++ b/thumbnails/go.mod @@ -8,6 +8,7 @@ require ( contrib.go.opencensus.io/exporter/zipkin v0.1.1 github.com/UnnoTed/fileb0x v1.1.4 github.com/cespare/reflex v0.2.0 + github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00 github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31 // indirect github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.4.3 @@ -31,7 +32,9 @@ require ( github.com/stretchr/testify v1.6.1 go.opencensus.io v0.22.5 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + golang.org/x/image v0.0.0-20190802002840-cff245a6509b google.golang.org/genproto v0.0.0-20200918140846-d0d605568037 // indirect + google.golang.org/grpc v1.33.1 gopkg.in/square/go-jose.v2 v2.5.1 ) diff --git a/thumbnails/go.sum b/thumbnails/go.sum index 78694c699d8..72349464762 100644 --- a/thumbnails/go.sum +++ b/thumbnails/go.sum @@ -1212,6 +1212,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/thumbnails/pkg/service/v0/service.go b/thumbnails/pkg/service/v0/service.go index 0b4194fa1ac..eb8357effe9 100644 --- a/thumbnails/pkg/service/v0/service.go +++ b/thumbnails/pkg/service/v0/service.go @@ -2,6 +2,7 @@ package svc import ( "context" + "image" "gopkg.in/square/go-jose.v2/jwt" @@ -10,7 +11,6 @@ import ( v0proto "github.com/owncloud/ocis/thumbnails/pkg/proto/v0" "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/pkg/errors" ) @@ -18,19 +18,19 @@ import ( func NewService(opts ...Option) v0proto.ThumbnailServiceHandler { options := newOptions(opts...) logger := options.Logger - resolutions, err := resolution.New(options.Config.Thumbnail.Resolutions) + resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) if err != nil { logger.Fatal().Err(err).Msg("resolutions not configured correctly") } svc := Thumbnail{ serviceID: options.Config.Server.Namespace + "." + options.Config.Server.Name, manager: thumbnail.NewSimpleManager( + resolutions, options.ThumbnailStorage, logger, ), - resolutions: resolutions, - source: options.ImageSource, - logger: logger, + source: options.ImageSource, + logger: logger, } return svc @@ -38,11 +38,10 @@ func NewService(opts ...Option) v0proto.ThumbnailServiceHandler { // Thumbnail implements the GRPC handler. type Thumbnail struct { - serviceID string - manager thumbnail.Manager - resolutions resolution.Resolutions - source imgsource.Source - logger log.Logger + serviceID string + manager thumbnail.Manager + source imgsource.Source + logger log.Logger } // GetThumbnail retrieves a thumbnail for an image @@ -52,7 +51,6 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs g.logger.Debug().Str("filetype", req.Filetype.String()).Msg("unsupported filetype") return nil } - r := g.resolutions.ClosestMatch(int(req.Width), int(req.Height)) auth := req.Authorization if auth == "" { @@ -64,8 +62,7 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs } tr := thumbnail.Request{ - Resolution: r, - ImagePath: req.Filepath, + Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), Encoder: encoder, ETag: req.Etag, Username: username, @@ -78,8 +75,8 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs return nil } - sCtx := imgsource.WithAuthorization(ctx, auth) - img, err := g.source.Get(sCtx, tr.ImagePath) + sCtx := imgsource.ContextSetAuthorization(ctx, auth) + img, err := g.source.Get(sCtx, req.Filepath) if err != nil { return merrors.InternalServerError(g.serviceID, "could not get image from source: %v", err.Error()) } diff --git a/thumbnails/pkg/thumbnail/imgsource/filesystem.go b/thumbnails/pkg/thumbnail/imgsource/filesystem.go index 22710b1cd63..f809ba30b92 100644 --- a/thumbnails/pkg/thumbnail/imgsource/filesystem.go +++ b/thumbnails/pkg/thumbnail/imgsource/filesystem.go @@ -2,12 +2,12 @@ package imgsource import ( "context" - "fmt" "image" "os" "path/filepath" "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/pkg/errors" ) // NewFileSystemSource return a new FileSystem instance @@ -27,12 +27,12 @@ func (s FileSystem) Get(ctx context.Context, file string) (image.Image, error) { imgPath := filepath.Join(s.basePath, file) f, err := os.Open(filepath.Clean(imgPath)) if err != nil { - return nil, fmt.Errorf("failed to load the file %s from %s error %s", file, imgPath, err.Error()) + return nil, errors.Wrapf(err, "failed to load the file %s from %s", file, imgPath) } img, _, err := image.Decode(f) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Get: Decode:") } return img, nil diff --git a/thumbnails/pkg/thumbnail/imgsource/imgsource.go b/thumbnails/pkg/thumbnail/imgsource/imgsource.go index 3b4adfd1395..394836a8002 100644 --- a/thumbnails/pkg/thumbnail/imgsource/imgsource.go +++ b/thumbnails/pkg/thumbnail/imgsource/imgsource.go @@ -16,15 +16,16 @@ type Source interface { Get(ctx context.Context, path string) (image.Image, error) } -// WithAuthorization puts the authorization in the context. -func WithAuthorization(parent context.Context, authorization string) context.Context { +// ContextSetAuthorization puts the authorization in the context. +func ContextSetAuthorization(parent context.Context, authorization string) context.Context { return context.WithValue(parent, auth, authorization) } -func authorization(ctx context.Context) string { +// ContextGetAuthorization gets the authorization from the context. +func ContextGetAuthorization(ctx context.Context) (string, bool) { val := ctx.Value(auth) if val == nil { - return "" + return "", false } - return val.(string) + return val.(string), true } diff --git a/thumbnails/pkg/thumbnail/imgsource/webdav.go b/thumbnails/pkg/thumbnail/imgsource/webdav.go index 56503395aac..eedcbc7f7ce 100644 --- a/thumbnails/pkg/thumbnail/imgsource/webdav.go +++ b/thumbnails/pkg/thumbnail/imgsource/webdav.go @@ -10,6 +10,7 @@ import ( "path" "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/pkg/errors" ) // NewWebDavSource creates a new webdav instance. @@ -32,13 +33,13 @@ func (s WebDav) Get(ctx context.Context, file string) (image.Image, error) { u.Path = path.Join(u.Path, file) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { - return nil, fmt.Errorf("could not get the image \"%s\" error: %s", file, err.Error()) + return nil, errors.Wrapf(err, `could not get the image "%s"`, file) } http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: s.insecure} - auth := authorization(ctx) - if auth == "" { + auth, ok := ContextGetAuthorization(ctx) + if !ok { return nil, fmt.Errorf("could not get image \"%s\" error: authorization is missing", file) } req.Header.Add("Authorization", auth) @@ -46,7 +47,7 @@ func (s WebDav) Get(ctx context.Context, file string) (image.Image, error) { client := &http.Client{} resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("could not get the image \"%s\" error: %s", file, err.Error()) + return nil, errors.Wrapf(err, `could not get the image "%s"`, file) } if resp.StatusCode != http.StatusOK { @@ -55,7 +56,7 @@ func (s WebDav) Get(ctx context.Context, file string) (image.Image, error) { img, _, err := image.Decode(resp.Body) if err != nil { - return nil, fmt.Errorf("could not decode the image \"%s\". error: %s", file, err.Error()) + return nil, errors.Wrapf(err, `could not decode the image "%s"`, file) } return img, nil } diff --git a/thumbnails/pkg/thumbnail/resolution/resolution.go b/thumbnails/pkg/thumbnail/resolution/resolution.go deleted file mode 100644 index 514fe857446..00000000000 --- a/thumbnails/pkg/thumbnail/resolution/resolution.go +++ /dev/null @@ -1,37 +0,0 @@ -package resolution - -import ( - "fmt" - "strconv" - "strings" -) - -// Parse parses a resolution string in the form x and returns a resolution instance. -func Parse(s string) (Resolution, error) { - parts := strings.Split(s, "x") - if len(parts) != 2 { - return Resolution{}, fmt.Errorf("failed to parse resolution: %s. Expected format x", s) - } - width, err := strconv.Atoi(parts[0]) - if err != nil { - return Resolution{}, fmt.Errorf("width: %s has an invalid value. Expected an integer", parts[0]) - } - height, err := strconv.Atoi(parts[1]) - if err != nil { - return Resolution{}, fmt.Errorf("height: %s has an invalid value. Expected an integer", parts[1]) - } - return Resolution{Width: width, Height: height}, nil -} - -// Resolution defines represents the width and height of a thumbnail. -type Resolution struct { - Width int - Height int -} - -// String returns the resolution in the format: -// -// x -func (r Resolution) String() string { - return strconv.Itoa(r.Width) + "x" + strconv.Itoa(r.Height) -} diff --git a/thumbnails/pkg/thumbnail/resolution/resolution_test.go b/thumbnails/pkg/thumbnail/resolution/resolution_test.go deleted file mode 100644 index b8c3e6c8832..00000000000 --- a/thumbnails/pkg/thumbnail/resolution/resolution_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package resolution - -import "testing" - -func TestParseWithEmptyString(t *testing.T) { - _, err := Parse("") - if err == nil { - t.Error("Parse with empty string should return an error.") - } -} - -func TestParseWithInvalidWidth(t *testing.T) { - _, err := Parse("invalidx42") - if err == nil { - t.Error("Parse with invalid width should return an error.") - } -} - -func TestParseWithInvalidHeight(t *testing.T) { - _, err := Parse("42xinvalid") - if err == nil { - t.Error("Parse with invalid height should return an error.") - } -} - -func TestParse(t *testing.T) { - rStr := "42x23" - r, _ := Parse(rStr) - if r.Width != 42 || r.Height != 23 { - t.Errorf("Expected resolution %s got %s", rStr, r.String()) - } -} - -func TestString(t *testing.T) { - r := Resolution{Width: 42, Height: 23} - expected := "42x23" - if r.String() != expected { - t.Errorf("Expected string %s got %s", expected, r.String()) - } -} diff --git a/thumbnails/pkg/thumbnail/resolution/resolutions.go b/thumbnails/pkg/thumbnail/resolution/resolutions.go deleted file mode 100644 index 2a83d45e7df..00000000000 --- a/thumbnails/pkg/thumbnail/resolution/resolutions.go +++ /dev/null @@ -1,74 +0,0 @@ -package resolution - -import ( - "fmt" - "math" - "sort" -) - -// New creates an instance of Resolutions from resolution strings. -func New(rStrs []string) (Resolutions, error) { - var rs Resolutions - for _, rStr := range rStrs { - r, err := Parse(rStr) - if err != nil { - return nil, fmt.Errorf("failed to initialize resolutions: %s", err.Error()) - } - rs = append(rs, r) - } - sort.Slice(rs, func(i, j int) bool { - left := rs[i] - right := rs[j] - - leftSize := left.Width * left.Height - rightSize := right.Width * right.Height - - return leftSize < rightSize - }) - - return rs, nil -} - -// Resolutions represents the available thumbnail resolutions. -type Resolutions []Resolution - -// ClosestMatch returns the resolution which is closest to the provided resolution. -// If there is no exact match the resolution will be the next higher one. -// If the given resolution is bigger than all available resolutions the biggest available one is used. -func (r Resolutions) ClosestMatch(width, height int) Resolution { - if len(r) == 0 { - return Resolution{Width: width, Height: height} - } - - isLandscape := width > height - givenLen := int(math.Max(float64(width), float64(height))) - - // Initialize with the first resolution - var match Resolution - minDiff := math.MaxInt32 - - for _, current := range r { - len := dimensionLength(current, isLandscape) - diff := givenLen - len - if diff > 0 { - continue - } - absDiff := int(math.Abs(float64(diff))) - if absDiff < minDiff { - minDiff = absDiff - match = current - } - } - - if match == (Resolution{}) { - match = r[len(r)-1] - } - return match -} - -func dimensionLength(r Resolution, landscape bool) int { - if landscape { - return r.Width - } - return r.Height -} diff --git a/thumbnails/pkg/thumbnail/resolution/resolutions_test.go b/thumbnails/pkg/thumbnail/resolution/resolutions_test.go deleted file mode 100644 index 1190cc942aa..00000000000 --- a/thumbnails/pkg/thumbnail/resolution/resolutions_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package resolution - -import ( - "testing" -) - -func TestInitWithEmptyArray(t *testing.T) { - rs, err := New([]string{}) - if err != nil { - t.Errorf("Init with an empty array should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 0 { - t.Error("Init with an empty array should return an empty Resolutions instance.\n") - } -} - -func TestInitWithNil(t *testing.T) { - rs, err := New(nil) - if err != nil { - t.Errorf("Init with nil parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 0 { - t.Error("Init with nil parameter should return an empty Resolutions instance.\n") - } -} - -func TestInitWithInvalidValuesInArray(t *testing.T) { - _, err := New([]string{"invalid"}) - if err == nil { - t.Error("Init with invalid parameter should fail.\n") - } -} - -func TestInit(t *testing.T) { - rs, err := New([]string{"16x16"}) - if err != nil { - t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != 1 { - t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), 1) - } -} - -func TestInitWithMultipleResolutions(t *testing.T) { - rStrs := []string{"16x16", "32x32", "64x64", "128x128"} - rs, err := New(rStrs) - if err != nil { - t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) - } - if len(rs) != len(rStrs) { - t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), len(rStrs)) - } -} - -func TestInitWithMultipleResolutionsShouldBeSorted(t *testing.T) { - rStrs := []string{"32x32", "64x64", "16x16", "128x128"} - rs, err := New(rStrs) - if err != nil { - t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) - } - - for i := 0; i < len(rs)-1; i++ { - current := rs[i] - currentSize := current.Width * current.Height - next := rs[i] - nextSize := next.Width * next.Height - - if currentSize > nextSize { - t.Error("Resolutions are not sorted.") - } - - } -} -func TestClosestMatchWithEmptyResolutions(t *testing.T) { - rs, _ := New(nil) - width := 24 - height := 24 - - r := rs.ClosestMatch(width, height) - if r.Width != width || r.Height != height { - t.Errorf("ClosestMatch from empty resolutions should return the given resolution") - } -} - -func TestClosestMatch(t *testing.T) { - rs, _ := New([]string{"16x16", "24x24", "32x32", "64x64", "128x128"}) - table := [][]int{ - // width, height, expectedWidth, expectedHeight - []int{17, 17, 24, 24}, - []int{12, 17, 24, 24}, - []int{24, 24, 24, 24}, - []int{20, 20, 24, 24}, - []int{20, 80, 128, 128}, - []int{80, 20, 128, 128}, - []int{48, 48, 64, 64}, - []int{1024, 1024, 128, 128}, - } - - for _, row := range table { - width := row[0] - height := row[1] - expectedWidth := row[2] - expectedHeight := row[3] - - match := rs.ClosestMatch(width, height) - - if match.Width != expectedWidth || match.Height != expectedHeight { - t.Errorf("Expected resolution %dx%d got %s", expectedWidth, expectedHeight, match.String()) - } - } -} diff --git a/thumbnails/pkg/thumbnail/resolutions.go b/thumbnails/pkg/thumbnail/resolutions.go new file mode 100644 index 00000000000..19ed6984c6e --- /dev/null +++ b/thumbnails/pkg/thumbnail/resolutions.go @@ -0,0 +1,110 @@ +package thumbnail + +import ( + "fmt" + "image" + "math" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const ( + _resolutionSeperator = "x" +) + +// ParseResolution returns an image.Rectangle representing the resolution given as a string +func ParseResolution(s string) (image.Rectangle, error) { + parts := strings.Split(s, _resolutionSeperator) + if len(parts) != 2 { + return image.Rectangle{}, fmt.Errorf("failed to parse resolution: %s. Expected format x", s) + } + width, err := strconv.Atoi(parts[0]) + if err != nil { + return image.Rectangle{}, fmt.Errorf("width: %s has an invalid value. Expected an integer", parts[0]) + } + height, err := strconv.Atoi(parts[1]) + if err != nil { + return image.Rectangle{}, fmt.Errorf("height: %s has an invalid value. Expected an integer", parts[1]) + } + return image.Rect(0, 0, width, height), nil +} + +// Resolutions is a list of image.Rectangle representing resolutions. +type Resolutions []image.Rectangle + +// ParseResolutions creates an instance of Resolutions from resolution strings. +func ParseResolutions(strs []string) (Resolutions, error) { + var rs Resolutions + for _, s := range strs { + r, err := ParseResolution(s) + if err != nil { + return nil, errors.Wrap(err, "could not parse resolutions") + } + rs = append(rs, r) + } + return rs, nil +} + +// ClosestMatch returns the resolution which is closest to the provided resolution. +// If there is no exact match the resolution will be the next higher one. +// If the given resolution is bigger than all available resolutions the biggest available one is used. +func (rs Resolutions) ClosestMatch(requested image.Rectangle, sourceSize image.Rectangle) image.Rectangle { + isLandscape := sourceSize.Dx() > sourceSize.Dy() + sourceLen := dimensionLength(sourceSize, isLandscape) + requestedLen := dimensionLength(requested, isLandscape) + isSourceSmaller := sourceLen < requestedLen + + // We don't want to scale images up. + if isSourceSmaller { + return sourceSize + } + + if len(rs) == 0 { + return requested + } + + var match image.Rectangle + // Since we want to search for the smallest difference we start with the highest possible number + minDiff := math.MaxInt32 + + for _, current := range rs { + cLen := dimensionLength(current, isLandscape) + diff := requestedLen - cLen + if diff > 0 { + // current is smaller + continue + } + + // Convert diff to positive value + // Multiplying by -1 is safe since we aren't getting postive numbers here + // because of the check above + absDiff := diff * -1 + if absDiff < minDiff { + minDiff = absDiff + match = current + } + } + + if (match == image.Rectangle{}) { + match = rs[len(rs)-1] + } + return match +} + +func mapRatio(given image.Rectangle, other image.Rectangle) image.Rectangle { + isLandscape := given.Dx() > given.Dy() + ratio := float64(given.Dx()) / float64(given.Dy()) + if isLandscape { + return image.Rect(0, 0, other.Dx(), int(float64(other.Dx())/ratio)) + } + return image.Rect(0, 0, int(float64(other.Dy())*ratio), other.Dy()) +} + +func dimensionLength(rect image.Rectangle, isLandscape bool) int { + if isLandscape { + return rect.Dx() + } + return rect.Dy() +} diff --git a/thumbnails/pkg/thumbnail/resolutions_test.go b/thumbnails/pkg/thumbnail/resolutions_test.go new file mode 100644 index 00000000000..d184fbdc000 --- /dev/null +++ b/thumbnails/pkg/thumbnail/resolutions_test.go @@ -0,0 +1,139 @@ +package thumbnail + +import ( + "image" + "testing" +) + +func TestInitWithEmptyArray(t *testing.T) { + rs, err := ParseResolutions([]string{}) + if err != nil { + t.Errorf("Init with an empty array should not fail. Error: %s.\n", err.Error()) + } + if len(rs) != 0 { + t.Error("Init with an empty array should return an empty Resolutions instance.\n") + } +} + +func TestInitWithNil(t *testing.T) { + rs, err := ParseResolutions(nil) + if err != nil { + t.Errorf("Init with nil parameter should not fail. Error: %s.\n", err.Error()) + } + if len(rs) != 0 { + t.Error("Init with nil parameter should return an empty Resolutions instance.\n") + } +} + +func TestInitWithInvalidValuesInArray(t *testing.T) { + _, err := ParseResolutions([]string{"invalid"}) + if err == nil { + t.Error("Init with invalid parameter should fail.\n") + } +} + +func TestInit(t *testing.T) { + rs, err := ParseResolutions([]string{"16x16"}) + if err != nil { + t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) + } + if len(rs) != 1 { + t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), 1) + } +} + +func TestInitWithMultipleResolutions(t *testing.T) { + rStrs := []string{"16x16", "32x32", "64x64", "128x128"} + rs, err := ParseResolutions(rStrs) + if err != nil { + t.Errorf("Init with valid parameter should not fail. Error: %s.\n", err.Error()) + } + if len(rs) != len(rStrs) { + t.Errorf("resolutions has size %d, expected size %d.\n", len(rs), len(rStrs)) + } +} + +func TestClosestMatchWithEmptyResolutions(t *testing.T) { + rs, _ := ParseResolutions(nil) + want := image.Rect(0, 0, 24, 24) + imgSize := image.Rect(0, 0, 24, 24) + + r := rs.ClosestMatch(want, imgSize) + if r.Dx() != want.Dx() || r.Dy() != want.Dy() { + t.Errorf("ClosestMatch from empty resolutions should return the given resolution") + } +} + +func TestClosestMatch(t *testing.T) { + rs, _ := ParseResolutions([]string{"16x16", "24x24", "32x32", "64x64", "128x128"}) + + testData := [][]image.Rectangle{ + {image.Rect(0, 0, 17, 17), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, + {image.Rect(0, 0, 12, 17), image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 24, 24)}, + {image.Rect(0, 0, 24, 24), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, + {image.Rect(0, 0, 20, 20), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 24, 24)}, + {image.Rect(0, 0, 20, 80), image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 128, 128)}, + {image.Rect(0, 0, 80, 20), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 128, 128)}, + {image.Rect(0, 0, 48, 48), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 64, 64)}, + {image.Rect(0, 0, 1024, 1024), image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 128, 128)}, + {image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 256, 36), image.Rect(0, 0, 256, 36)}, + } + + for _, row := range testData { + given := row[0] + imgSize := row[1] + expected := row[2] + + match := rs.ClosestMatch(given, imgSize) + + if match != expected { + t.Errorf("Expected resolution %dx%d got %dx%d", expected.Dx(), expected.Dy(), match.Dx(), match.Dy()) + } + } +} + +func TestParseWithEmptyString(t *testing.T) { + _, err := ParseResolution("") + if err == nil { + t.Error("Parse with empty string should return an error.") + } +} + +func TestParseWithInvalidWidth(t *testing.T) { + _, err := ParseResolution("invalidx42") + if err == nil { + t.Error("Parse with invalid width should return an error.") + } +} + +func TestParseWithInvalidHeight(t *testing.T) { + _, err := ParseResolution("42xinvalid") + if err == nil { + t.Error("Parse with invalid height should return an error.") + } +} + +func TestParseResolution(t *testing.T) { + rStr := "42x23" + r, _ := ParseResolution(rStr) + if r.Dx() != 42 || r.Dy() != 23 { + t.Errorf("Expected resolution %s got %s", rStr, r.String()) + } +} + +func TestMapRatio(t *testing.T) { + testData := [][]image.Rectangle{ + {image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 32, 18)}, + {image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 18, 32)}, + {image.Rect(0, 0, 1024, 735), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 32, 22)}, + } + for _, row := range testData { + given := row[0] + other := row[1] + expected := row[2] + mapped := mapRatio(given, other) + if mapped.Dx() != expected.Dx() || mapped.Dy() != expected.Dy() { + t.Errorf("Expected %dx%d got %dx%d", expected.Dx(), expected.Dy(), mapped.Dx(), mapped.Dy()) + } + } +} diff --git a/thumbnails/pkg/thumbnail/storage/filesystem.go b/thumbnails/pkg/thumbnail/storage/filesystem.go index bd6063c39d4..4bf2a297a10 100644 --- a/thumbnails/pkg/thumbnail/storage/filesystem.go +++ b/thumbnails/pkg/thumbnail/storage/filesystem.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "sync" @@ -70,7 +71,7 @@ func (s *FileSystem) Set(username string, key string, img []byte) error { func (s *FileSystem) BuildKey(r Request) string { etag := r.ETag filetype := r.Types[0] - filename := r.Resolution.String() + "." + filetype + filename := strconv.Itoa(r.Resolution.Dx()) + "x" + strconv.Itoa(r.Resolution.Dy()) + "." + filetype return filepath.Join(etag[:2], etag[2:4], etag[4:], filename) } diff --git a/thumbnails/pkg/thumbnail/storage/storage.go b/thumbnails/pkg/thumbnail/storage/storage.go index 5b17c25de4a..1ec71cc7616 100644 --- a/thumbnails/pkg/thumbnail/storage/storage.go +++ b/thumbnails/pkg/thumbnail/storage/storage.go @@ -1,12 +1,14 @@ package storage -import "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/resolution" +import ( + "image" +) // Request combines different attributes needed for storage operations. type Request struct { ETag string Types []string - Resolution resolution.Resolution + Resolution image.Rectangle } // Storage defines the interface for a thumbnail store. diff --git a/thumbnails/pkg/thumbnail/thumbnails.go b/thumbnails/pkg/thumbnail/thumbnail.go similarity index 71% rename from thumbnails/pkg/thumbnail/thumbnails.go rename to thumbnails/pkg/thumbnail/thumbnail.go index 481e4b5564e..1f12d4f56bf 100644 --- a/thumbnails/pkg/thumbnail/thumbnails.go +++ b/thumbnails/pkg/thumbnail/thumbnail.go @@ -4,16 +4,14 @@ import ( "bytes" "image" - "github.com/nfnt/resize" "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/resolution" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" + "golang.org/x/image/draw" ) // Request bundles information needed to generate a thumbnail for afile type Request struct { - Resolution resolution.Resolution - ImagePath string + Resolution image.Rectangle Encoder Encoder ETag string Username string @@ -29,22 +27,25 @@ type Manager interface { } // NewSimpleManager creates a new instance of SimpleManager -func NewSimpleManager(storage storage.Storage, logger log.Logger) SimpleManager { +func NewSimpleManager(resolutions Resolutions, storage storage.Storage, logger log.Logger) SimpleManager { return SimpleManager{ - storage: storage, - logger: logger, + storage: storage, + logger: logger, + resolutions: resolutions, } } // SimpleManager is a simple implementation of Manager type SimpleManager struct { - storage storage.Storage - logger log.Logger + storage storage.Storage + logger log.Logger + resolutions Resolutions } // Get implements the Get Method of Manager func (s SimpleManager) Get(r Request, img image.Image) ([]byte, error) { - thumbnail := s.generate(r, img) + match := s.resolutions.ClosestMatch(r.Resolution, img.Bounds()) + thumbnail := s.generate(match, img) key := s.storage.BuildKey(mapToStorageRequest(r)) @@ -69,8 +70,10 @@ func (s SimpleManager) GetStored(r Request) []byte { return stored } -func (s SimpleManager) generate(r Request, img image.Image) image.Image { - thumbnail := resize.Thumbnail(uint(r.Resolution.Width), uint(r.Resolution.Height), img, resize.Lanczos2) +func (s SimpleManager) generate(r image.Rectangle, img image.Image) image.Image { + targetResolution := mapRatio(img.Bounds(), r) + thumbnail := image.NewRGBA(targetResolution) + draw.ApproxBiLinear.Scale(thumbnail, targetResolution, img, img.Bounds(), draw.Over, nil) return thumbnail } diff --git a/thumbnails/pkg/thumbnail/thumbnail_test.go b/thumbnails/pkg/thumbnail/thumbnail_test.go new file mode 100644 index 00000000000..270630704b4 --- /dev/null +++ b/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -0,0 +1,47 @@ +package thumbnail + +import ( + "image" + "os" + "path/filepath" + "testing" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" +) + +type NoOpManager struct { + storage.Storage +} + +func (m NoOpManager) BuildKey(r storage.Request) string { + return "" +} + +func (m NoOpManager) Set(username, key string, thumbnail []byte) error { + return nil +} + +func BenchmarkGet(b *testing.B) { + + sut := NewSimpleManager( + Resolutions{}, + NoOpManager{}, + log.NewLogger(), + ) + + res, _ := ParseResolution("32x32") + req := Request{ + Resolution: res, + ETag: "1872ade88f3013edeb33decd74a4f947", + } + cwd, _ := os.Getwd() + p := filepath.Join(cwd, "../../testdata/oc.png") + f, _ := os.Open(p) + defer f.Close() + img, ext, _ := image.Decode(f) + req.Encoder = EncoderForType(ext) + for i := 0; i < b.N; i++ { + sut.Get(req, img) + } +}