From d6182a4ea1ab0719252ac189f445e0295efbe631 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Fri, 4 Mar 2022 19:13:48 +0100 Subject: [PATCH 1/2] generate animated thumbnails for animated gifs --- .../services/thumbnails/v0/thumbnails.pb.go | 82 ++++++++++--------- .../thumbnails/v0/thumbnails.swagger.json | 3 +- .../services/thumbnails/v0/thumbnails.proto | 1 + thumbnails/pkg/preprocessor/preprocessor.go | 19 ++++- thumbnails/pkg/service/v0/service.go | 20 +++-- thumbnails/pkg/thumbnail/encoding.go | 70 +++++++++++++--- thumbnails/pkg/thumbnail/encoding_test.go | 2 +- thumbnails/pkg/thumbnail/generator.go | 65 +++++++++++++++ thumbnails/pkg/thumbnail/thumbnail.go | 32 +++++--- thumbnails/pkg/thumbnail/thumbnail_test.go | 4 +- webdav/pkg/service/v0/service.go | 4 +- 11 files changed, 223 insertions(+), 79 deletions(-) create mode 100644 thumbnails/pkg/thumbnail/generator.go diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go index cb2dd1d8639..ac70eb222e8 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go @@ -28,6 +28,7 @@ type GetThumbnailRequest_ThumbnailType int32 const ( GetThumbnailRequest_PNG GetThumbnailRequest_ThumbnailType = 0 // Represents PNG type GetThumbnailRequest_JPG GetThumbnailRequest_ThumbnailType = 1 // Represents JPG type + GetThumbnailRequest_GIF GetThumbnailRequest_ThumbnailType = 2 // Represents GIF type ) // Enum value maps for GetThumbnailRequest_ThumbnailType. @@ -35,10 +36,12 @@ var ( GetThumbnailRequest_ThumbnailType_name = map[int32]string{ 0: "PNG", 1: "JPG", + 2: "GIF", } GetThumbnailRequest_ThumbnailType_value = map[string]int32{ "PNG": 0, "JPG": 1, + "GIF": 2, } ) @@ -257,7 +260,7 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8e, 0x03, 0x0a, 0x13, 0x47, 0x65, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x03, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x65, 0x0a, @@ -279,47 +282,48 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x21, 0x0a, + 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x2a, 0x0a, 0x0d, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x50, 0x47, 0x10, 0x01, - 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, - 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, - 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, - 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xeb, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x12, 0x07, 0x0a, 0x03, 0x47, 0x49, 0x46, 0x10, 0x02, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, + 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, + 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, + 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, + 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, + 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, + 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, + 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, + 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0xeb, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, + 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, + 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, + 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, + 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, + 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, + 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, - 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, - 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, - 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, - 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, - 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, - 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, - 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, - 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, - 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, - 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, - 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, - 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, - 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, - 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, - 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, - 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, + 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, + 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, + 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, + 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, + 0x12, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json index f79a4f11908..aef36cc0953 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json @@ -34,7 +34,8 @@ "type": "string", "enum": [ "PNG", - "JPG" + "JPG", + "GIF" ], "default": "PNG", "description": "The file types to which the thumbnail can get encoded to." diff --git a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto index 0b7eae51715..c8141aedba3 100644 --- a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto +++ b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto @@ -45,6 +45,7 @@ message GetThumbnailRequest { enum ThumbnailType { PNG = 0; // Represents PNG type JPG = 1; // Represents JPG type + GIF = 2; // Represents GIF type } // The type to which the thumbnail should get encoded to. ThumbnailType thumbnail_type = 2; diff --git a/thumbnails/pkg/preprocessor/preprocessor.go b/thumbnails/pkg/preprocessor/preprocessor.go index f521967d57b..d0b1acc6f93 100644 --- a/thumbnails/pkg/preprocessor/preprocessor.go +++ b/thumbnails/pkg/preprocessor/preprocessor.go @@ -4,6 +4,7 @@ import ( "bufio" "image" "image/draw" + "image/gif" "io" "math" "mime" @@ -16,12 +17,12 @@ import ( ) type FileConverter interface { - Convert(r io.Reader) (image.Image, error) + Convert(r io.Reader) (interface{}, error) } type ImageDecoder struct{} -func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) { +func (i ImageDecoder) Convert(r io.Reader) (interface{}, error) { img, _, err := image.Decode(r) if err != nil { return nil, errors.Wrap(err, `could not decode the image`) @@ -29,11 +30,21 @@ func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) { return img, nil } +type GifDecoder struct{} + +func (i GifDecoder) Convert(r io.Reader) (interface{}, error) { + img, err := gif.DecodeAll(r) + if err != nil { + return nil, errors.Wrap(err, `could not decode the image`) + } + return img, nil +} + type TxtToImageConverter struct { fontLoader *FontLoader } -func (t TxtToImageConverter) Convert(r io.Reader) (image.Image, error) { +func (t TxtToImageConverter) Convert(r io.Reader) (interface{}, error) { img := image.NewRGBA(image.Rect(0, 0, 640, 480)) imgBounds := img.Bounds() @@ -183,6 +194,8 @@ func ForType(mimeType string, opts map[string]interface{}) FileConverter { return TxtToImageConverter{ fontLoader: fontLoader, } + case "image/gif": + return GifDecoder{} default: return ImageDecoder{} } diff --git a/thumbnails/pkg/service/v0/service.go b/thumbnails/pkg/service/v0/service.go index 87c638f2255..32f9688ceeb 100644 --- a/thumbnails/pkg/service/v0/service.go +++ b/thumbnails/pkg/service/v0/service.go @@ -71,19 +71,23 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") return nil } - encoder := thumbnail.EncoderForType(req.ThumbnailType.String()) - if encoder == nil { + generator, err := thumbnail.GeneratorForType(req.ThumbnailType.String()) + if err != nil { + g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") + return nil + } + encoder, err := thumbnail.EncoderForType(req.ThumbnailType.String()) + if err != nil { g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") return nil } var thumb []byte - var err error switch { case req.GetWebdavSource() != nil: - thumb, err = g.handleWebdavSource(ctx, req, encoder) + thumb, err = g.handleWebdavSource(ctx, req, generator, encoder) case req.GetCs3Source() != nil: - thumb, err = g.handleCS3Source(ctx, req, encoder) + thumb, err = g.handleCS3Source(ctx, req, generator, encoder) default: g.logger.Error().Msg("no image source provided") return merrors.BadRequest(g.serviceID, "image source is missing") @@ -97,7 +101,7 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb return nil } -func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { src := req.GetCs3Source() sRes, err := g.stat(src.Path, src.Authorization) if err != nil { @@ -106,6 +110,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh tr := thumbnail.Request{ Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, Encoder: encoder, Checksum: sRes.GetInfo().GetChecksum().GetSum(), } @@ -136,7 +141,7 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh return thumb, nil } -func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { src := req.GetWebdavSource() imgURL, err := url.Parse(src.Url) if err != nil { @@ -182,6 +187,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge } tr := thumbnail.Request{ Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), + Generator: generator, Encoder: encoder, Checksum: sRes.GetInfo().GetChecksum().GetSum(), } diff --git a/thumbnails/pkg/thumbnail/encoding.go b/thumbnails/pkg/thumbnail/encoding.go index b253ca3b5fb..d9c43d2eceb 100644 --- a/thumbnails/pkg/thumbnail/encoding.go +++ b/thumbnails/pkg/thumbnail/encoding.go @@ -1,17 +1,33 @@ package thumbnail import ( + "errors" "image" + "image/gif" "image/jpeg" "image/png" "io" "strings" ) +const ( + typePng = "png" + typeJpg = "jpg" + typeJpeg = "jpeg" + typeGif = "gif" +) + +var ( + // ErrInvalidType represents the error when a type can't be encoded. + ErrInvalidType = errors.New("can't encode this type") + // ErrNoEncoderForType represents the error when an encoder couldn't be found for a type. + ErrNoEncoderForType = errors.New("no encoder for this type found") +) + // Encoder encodes the thumbnail to a specific format. type Encoder interface { // Encode encodes the image to a format. - Encode(io.Writer, image.Image) error + Encode(io.Writer, interface{}) error // Types returns the formats suffixes. Types() []string // MimeType returns the mimetype used by the encoder. @@ -22,13 +38,17 @@ type Encoder interface { type PngEncoder struct{} // Encode encodes to png format -func (e PngEncoder) Encode(w io.Writer, i image.Image) error { - return png.Encode(w, i) +func (e PngEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return ErrInvalidType + } + return png.Encode(w, m) } // Types returns the png suffix func (e PngEncoder) Types() []string { - return []string{"png"} + return []string{typePng} } // MimeType returns the mimetype for png files. @@ -40,13 +60,17 @@ func (e PngEncoder) MimeType() string { type JpegEncoder struct{} // Encode encodes to jpg -func (e JpegEncoder) Encode(w io.Writer, i image.Image) error { - return jpeg.Encode(w, i, nil) +func (e JpegEncoder) Encode(w io.Writer, img interface{}) error { + m, ok := img.(image.Image) + if !ok { + return ErrInvalidType + } + return jpeg.Encode(w, m, nil) } // Types returns the jpg suffixes. func (e JpegEncoder) Types() []string { - return []string{"jpeg", "jpg"} + return []string{typeJpeg, typeJpg} } // MimeType returns the mimetype for jpg files. @@ -54,15 +78,35 @@ func (e JpegEncoder) MimeType() string { return "image/jpeg" } +type GifEncoder struct{} + +func (e GifEncoder) Encode(w io.Writer, img interface{}) error { + g, ok := img.(*gif.GIF) + if !ok { + return ErrInvalidType + } + return gif.EncodeAll(w, g) +} + +func (e GifEncoder) Types() []string { + return []string{typeGif} +} + +func (e GifEncoder) MimeType() string { + return "image/gif" +} + // EncoderForType returns the encoder for a given file type // or nil if the type is not supported. -func EncoderForType(fileType string) Encoder { +func EncoderForType(fileType string) (Encoder, error) { switch strings.ToLower(fileType) { - case "png": - return PngEncoder{} - case "jpg", "jpeg": - return JpegEncoder{} + case typePng: + return PngEncoder{}, nil + case typeJpg, typeJpeg: + return JpegEncoder{}, nil + case typeGif: + return GifEncoder{}, nil default: - return nil + return nil, ErrNoEncoderForType } } diff --git a/thumbnails/pkg/thumbnail/encoding_test.go b/thumbnails/pkg/thumbnail/encoding_test.go index 1dfbef60aa6..6dcce72a621 100644 --- a/thumbnails/pkg/thumbnail/encoding_test.go +++ b/thumbnails/pkg/thumbnail/encoding_test.go @@ -14,7 +14,7 @@ func TestEncoderForType(t *testing.T) { } for k, v := range table { - e := EncoderForType(k) + e, _ := EncoderForType(k) if e != v { t.Fail() } diff --git a/thumbnails/pkg/thumbnail/generator.go b/thumbnails/pkg/thumbnail/generator.go new file mode 100644 index 00000000000..6bea9b87a3b --- /dev/null +++ b/thumbnails/pkg/thumbnail/generator.go @@ -0,0 +1,65 @@ +package thumbnail + +import ( + "errors" + "image" + "image/draw" + "image/gif" + "strings" + + "github.com/disintegration/imaging" +) + +var ( + // ErrInvalidType represents the error when a type can't be encoded. + ErrInvalidType2 = errors.New("can't encode this type") + // ErrNoGeneratorForType represents the error when no generator could be found for a type. + ErrNoGeneratorForType = errors.New("no generator for this type found") +) + +type Generator interface { + GenerateThumbnail(image.Rectangle, interface{}) (interface{}, error) +} + +type SimpleGenerator struct{} + +func (g SimpleGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(image.Image) + if !ok { + return nil, ErrInvalidType2 + } + + return imaging.Thumbnail(m, size.Dx(), size.Dy(), imaging.Lanczos), nil +} + +type GifGenerator struct{} + +func (g GifGenerator) GenerateThumbnail(size image.Rectangle, img interface{}) (interface{}, error) { + m, ok := img.(*gif.GIF) + if !ok { + return nil, ErrInvalidType2 + } + var bounds image.Rectangle + for i := range m.Image { + img := imaging.Resize(m.Image[i], size.Dx(), size.Dy(), imaging.Lanczos) + bounds = image.Rect(0, 0, size.Dx(), size.Dy()) + m.Image[i] = image.NewPaletted(bounds, m.Image[i].Palette) + draw.Draw(m.Image[i], bounds, img, image.Pt(0, 0), draw.Src) + } + m.Config.Height = bounds.Dy() + m.Config.Width = bounds.Dx() + return m, nil +} + +// GeneratorForType returns the generator for a given file type +// or nil if the type is not supported. +func GeneratorForType(fileType string) (Generator, error) { + switch strings.ToLower(fileType) { + case typePng, typeJpg, typeJpeg: + return SimpleGenerator{}, nil + case typeGif: + return GifGenerator{}, nil + default: + return nil, ErrNoEncoderForType + } +} diff --git a/thumbnails/pkg/thumbnail/thumbnail.go b/thumbnails/pkg/thumbnail/thumbnail.go index 815c8928a33..ceeca03f410 100644 --- a/thumbnails/pkg/thumbnail/thumbnail.go +++ b/thumbnails/pkg/thumbnail/thumbnail.go @@ -2,12 +2,13 @@ package thumbnail import ( "bytes" - "github.com/disintegration/imaging" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" "image" + "image/gif" "mime" "strings" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" ) var ( @@ -24,13 +25,14 @@ var ( type Request struct { Resolution image.Rectangle Encoder Encoder + Generator Generator Checksum string } // Manager is responsible for generating thumbnails type Manager interface { // Generate will return a thumbnail for a file - Generate(Request, image.Image) ([]byte, error) + Generate(Request, interface{}) ([]byte, error) // Get loads the thumbnail from the storage. // It will return nil if no image is stored for the given context. Get(Request) ([]byte, bool) @@ -54,12 +56,22 @@ type SimpleManager struct { // Generate creates a thumbnail and stores it. // The created thumbnail is also being returned. -func (s SimpleManager) Generate(r Request, img image.Image) ([]byte, error) { - match := s.resolutions.ClosestMatch(r.Resolution, img.Bounds()) - thumbnail := s.generate(match, img) +func (s SimpleManager) Generate(r Request, img interface{}) ([]byte, error) { + var match image.Rectangle + switch m := img.(type) { + case *gif.GIF: + match = s.resolutions.ClosestMatch(r.Resolution, m.Image[0].Bounds()) + case image.Image: + match = s.resolutions.ClosestMatch(r.Resolution, m.Bounds()) + } + + thumbnail, err := r.Generator.GenerateThumbnail(match, img) + if err != nil { + return nil, err + } dst := new(bytes.Buffer) - err := r.Encoder.Encode(dst, thumbnail) + err = r.Encoder.Encode(dst, thumbnail) if err != nil { return nil, err } @@ -79,10 +91,6 @@ func (s SimpleManager) Get(r Request) ([]byte, bool) { return s.storage.Get(k) } -func (s SimpleManager) generate(r image.Rectangle, img image.Image) image.Image { - return imaging.Thumbnail(img, r.Dx(), r.Dy(), imaging.Lanczos) -} - func mapToStorageRequest(r Request) storage.Request { return storage.Request{ Checksum: r.Checksum, diff --git a/thumbnails/pkg/thumbnail/thumbnail_test.go b/thumbnails/pkg/thumbnail/thumbnail_test.go index 94bf0a27733..76be41a6688 100644 --- a/thumbnails/pkg/thumbnail/thumbnail_test.go +++ b/thumbnails/pkg/thumbnail/thumbnail_test.go @@ -33,14 +33,14 @@ func BenchmarkGet(b *testing.B) { res, _ := ParseResolution("32x32") req := Request{ Resolution: res, - Checksum: "1872ade88f3013edeb33decd74a4f947", + Checksum: "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) + req.Encoder, _ = EncoderForType(ext) for i := 0; i < b.N; i++ { _, _ = sut.Generate(req, img) } diff --git a/webdav/pkg/service/v0/service.go b/webdav/pkg/service/v0/service.go index 40eefeff01d..c1d92011d5a 100644 --- a/webdav/pkg/service/v0/service.go +++ b/webdav/pkg/service/v0/service.go @@ -286,7 +286,9 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { func extensionToThumbnailType(ext string) thumbnailssvc.GetThumbnailRequest_ThumbnailType { switch strings.ToUpper(ext) { - case "GIF", "PNG": + case "GIF": + return thumbnailssvc.GetThumbnailRequest_GIF + case "PNG": return thumbnailssvc.GetThumbnailRequest_PNG default: return thumbnailssvc.GetThumbnailRequest_JPG From 95ae3b8762ecb283dae870f18abad6aaad0ece6e Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 8 Mar 2022 23:08:24 +0100 Subject: [PATCH 2/2] rewrite thumbnails API Improved the thumbnails API so that the binary files won't be transported via GRPC. GRPC has a limited message size and isn't very effiefficient with large binary data. --- changelog/unreleased/thumbnails-api.md | 7 + .../messages/thumbnails/v0/thumbnails.pb.go | 74 +++++- .../services/thumbnails/v0/thumbnails.pb.go | 214 +++++++----------- .../thumbnails/v0/thumbnails.swagger.json | 29 +-- .../messages/thumbnails/v0/thumbnails.proto | 7 + .../services/thumbnails/v0/thumbnails.proto | 16 +- thumbnails/pkg/command/server.go | 27 ++- thumbnails/pkg/config/config.go | 3 + thumbnails/pkg/config/defaultconfig.go | 7 + thumbnails/pkg/config/http.go | 8 + thumbnails/pkg/server/grpc/server.go | 4 +- thumbnails/pkg/server/http/option.go | 69 ++++++ thumbnails/pkg/server/http/server.go | 58 +++++ .../service/{ => grpc}/v0/decorators/base.go | 0 .../{ => grpc}/v0/decorators/instrument.go | 0 .../{ => grpc}/v0/decorators/logging.go | 0 .../{ => grpc}/v0/decorators/tracing.go | 0 .../pkg/service/{ => grpc}/v0/option.go | 0 .../pkg/service/{ => grpc}/v0/service.go | 116 ++++++---- thumbnails/pkg/service/http/v0/instrument.go | 30 +++ thumbnails/pkg/service/http/v0/logging.go | 30 +++ thumbnails/pkg/service/http/v0/option.go | 59 +++++ thumbnails/pkg/service/http/v0/service.go | 123 ++++++++++ thumbnails/pkg/service/http/v0/tracing.go | 28 +++ thumbnails/pkg/service/jwt/jwt.go | 8 + .../pkg/thumbnail/storage/filesystem.go | 34 +-- thumbnails/pkg/thumbnail/storage/inmemory.go | 9 +- thumbnails/pkg/thumbnail/storage/storage.go | 7 +- thumbnails/pkg/thumbnail/thumbnail.go | 66 +++--- webdav/pkg/service/v0/service.go | 95 ++++---- 30 files changed, 818 insertions(+), 310 deletions(-) create mode 100644 changelog/unreleased/thumbnails-api.md create mode 100644 thumbnails/pkg/config/http.go create mode 100644 thumbnails/pkg/server/http/option.go create mode 100644 thumbnails/pkg/server/http/server.go rename thumbnails/pkg/service/{ => grpc}/v0/decorators/base.go (100%) rename thumbnails/pkg/service/{ => grpc}/v0/decorators/instrument.go (100%) rename thumbnails/pkg/service/{ => grpc}/v0/decorators/logging.go (100%) rename thumbnails/pkg/service/{ => grpc}/v0/decorators/tracing.go (100%) rename thumbnails/pkg/service/{ => grpc}/v0/option.go (100%) rename thumbnails/pkg/service/{ => grpc}/v0/service.go (67%) create mode 100644 thumbnails/pkg/service/http/v0/instrument.go create mode 100644 thumbnails/pkg/service/http/v0/logging.go create mode 100644 thumbnails/pkg/service/http/v0/option.go create mode 100644 thumbnails/pkg/service/http/v0/service.go create mode 100644 thumbnails/pkg/service/http/v0/tracing.go create mode 100644 thumbnails/pkg/service/jwt/jwt.go diff --git a/changelog/unreleased/thumbnails-api.md b/changelog/unreleased/thumbnails-api.md new file mode 100644 index 00000000000..9ba858b0738 --- /dev/null +++ b/changelog/unreleased/thumbnails-api.md @@ -0,0 +1,7 @@ +Enhancement: Improve thumbnails API + +Changed the thumbnails API to no longer transfer images via GRPC. +GRPC has a limited message size and isn't very efficient with large binary data. +The new API transports the images over HTTP. + +https://github.com/owncloud/ocis/pull/3272 diff --git a/protogen/gen/ocis/messages/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/messages/thumbnails/v0/thumbnails.pb.go index 1fab202edf7..25721838c88 100644 --- a/protogen/gen/ocis/messages/thumbnails/v0/thumbnails.pb.go +++ b/protogen/gen/ocis/messages/thumbnails/v0/thumbnails.pb.go @@ -20,6 +20,56 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// The file types to which the thumbnail can be encoded to. +type ThumbnailType int32 + +const ( + ThumbnailType_PNG ThumbnailType = 0 // Represents PNG type + ThumbnailType_JPG ThumbnailType = 1 // Represents JPG type + ThumbnailType_GIF ThumbnailType = 2 // Represents GIF type +) + +// Enum value maps for ThumbnailType. +var ( + ThumbnailType_name = map[int32]string{ + 0: "PNG", + 1: "JPG", + 2: "GIF", + } + ThumbnailType_value = map[string]int32{ + "PNG": 0, + "JPG": 1, + "GIF": 2, + } +) + +func (x ThumbnailType) Enum() *ThumbnailType { + p := new(ThumbnailType) + *p = x + return p +} + +func (x ThumbnailType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ThumbnailType) Descriptor() protoreflect.EnumDescriptor { + return file_ocis_messages_thumbnails_v0_thumbnails_proto_enumTypes[0].Descriptor() +} + +func (ThumbnailType) Type() protoreflect.EnumType { + return &file_ocis_messages_thumbnails_v0_thumbnails_proto_enumTypes[0] +} + +func (x ThumbnailType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ThumbnailType.Descriptor instead. +func (ThumbnailType) EnumDescriptor() ([]byte, []int) { + return file_ocis_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{0} +} + type WebdavSource struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -184,12 +234,15 @@ var file_ocis_messages_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, - 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, - 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x2a, 0x0a, 0x0d, 0x54, 0x68, 0x75, + 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x4e, + 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x50, 0x47, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, + 0x47, 0x49, 0x46, 0x10, 0x02, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, + 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, + 0x63, 0x69, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, + 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -204,10 +257,12 @@ func file_ocis_messages_thumbnails_v0_thumbnails_proto_rawDescGZIP() []byte { return file_ocis_messages_thumbnails_v0_thumbnails_proto_rawDescData } +var file_ocis_messages_thumbnails_v0_thumbnails_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_ocis_messages_thumbnails_v0_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_ocis_messages_thumbnails_v0_thumbnails_proto_goTypes = []interface{}{ - (*WebdavSource)(nil), // 0: ocis.messages.thumbnails.v0.WebdavSource - (*CS3Source)(nil), // 1: ocis.messages.thumbnails.v0.CS3Source + (ThumbnailType)(0), // 0: ocis.messages.thumbnails.v0.ThumbnailType + (*WebdavSource)(nil), // 1: ocis.messages.thumbnails.v0.WebdavSource + (*CS3Source)(nil), // 2: ocis.messages.thumbnails.v0.CS3Source } var file_ocis_messages_thumbnails_v0_thumbnails_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -253,13 +308,14 @@ func file_ocis_messages_thumbnails_v0_thumbnails_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ocis_messages_thumbnails_v0_thumbnails_proto_rawDesc, - NumEnums: 0, + NumEnums: 1, NumMessages: 2, NumExtensions: 0, NumServices: 0, }, GoTypes: file_ocis_messages_thumbnails_v0_thumbnails_proto_goTypes, DependencyIndexes: file_ocis_messages_thumbnails_v0_thumbnails_proto_depIdxs, + EnumInfos: file_ocis_messages_thumbnails_v0_thumbnails_proto_enumTypes, MessageInfos: file_ocis_messages_thumbnails_v0_thumbnails_proto_msgTypes, }.Build() File_ocis_messages_thumbnails_v0_thumbnails_proto = out.File diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go index ac70eb222e8..cf949fb4563 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.pb.go @@ -22,56 +22,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// The file types to which the thumbnail can get encoded to. -type GetThumbnailRequest_ThumbnailType int32 - -const ( - GetThumbnailRequest_PNG GetThumbnailRequest_ThumbnailType = 0 // Represents PNG type - GetThumbnailRequest_JPG GetThumbnailRequest_ThumbnailType = 1 // Represents JPG type - GetThumbnailRequest_GIF GetThumbnailRequest_ThumbnailType = 2 // Represents GIF type -) - -// Enum value maps for GetThumbnailRequest_ThumbnailType. -var ( - GetThumbnailRequest_ThumbnailType_name = map[int32]string{ - 0: "PNG", - 1: "JPG", - 2: "GIF", - } - GetThumbnailRequest_ThumbnailType_value = map[string]int32{ - "PNG": 0, - "JPG": 1, - "GIF": 2, - } -) - -func (x GetThumbnailRequest_ThumbnailType) Enum() *GetThumbnailRequest_ThumbnailType { - p := new(GetThumbnailRequest_ThumbnailType) - *p = x - return p -} - -func (x GetThumbnailRequest_ThumbnailType) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (GetThumbnailRequest_ThumbnailType) Descriptor() protoreflect.EnumDescriptor { - return file_ocis_services_thumbnails_v0_thumbnails_proto_enumTypes[0].Descriptor() -} - -func (GetThumbnailRequest_ThumbnailType) Type() protoreflect.EnumType { - return &file_ocis_services_thumbnails_v0_thumbnails_proto_enumTypes[0] -} - -func (x GetThumbnailRequest_ThumbnailType) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use GetThumbnailRequest_ThumbnailType.Descriptor instead. -func (GetThumbnailRequest_ThumbnailType) EnumDescriptor() ([]byte, []int) { - return file_ocis_services_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{0, 0} -} - // A request to retrieve a thumbnail type GetThumbnailRequest struct { state protoimpl.MessageState @@ -81,7 +31,7 @@ type GetThumbnailRequest struct { // The path to the source image Filepath string `protobuf:"bytes,1,opt,name=filepath,proto3" json:"filepath,omitempty"` // The type to which the thumbnail should get encoded to. - ThumbnailType GetThumbnailRequest_ThumbnailType `protobuf:"varint,2,opt,name=thumbnail_type,json=thumbnailType,proto3,enum=ocis.services.thumbnails.v0.GetThumbnailRequest_ThumbnailType" json:"thumbnail_type,omitempty"` + ThumbnailType v0.ThumbnailType `protobuf:"varint,2,opt,name=thumbnail_type,json=thumbnailType,proto3,enum=ocis.messages.thumbnails.v0.ThumbnailType" json:"thumbnail_type,omitempty"` // The width of the thumbnail Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"` // The height of the thumbnail @@ -131,11 +81,11 @@ func (x *GetThumbnailRequest) GetFilepath() string { return "" } -func (x *GetThumbnailRequest) GetThumbnailType() GetThumbnailRequest_ThumbnailType { +func (x *GetThumbnailRequest) GetThumbnailType() v0.ThumbnailType { if x != nil { return x.ThumbnailType } - return GetThumbnailRequest_PNG + return v0.ThumbnailType(0) } func (x *GetThumbnailRequest) GetWidth() int32 { @@ -195,10 +145,12 @@ type GetThumbnailResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The thumbnail as a binary - Thumbnail []byte `protobuf:"bytes,1,opt,name=thumbnail,proto3" json:"thumbnail,omitempty"` + // The endpoint where the thumbnail can be downloaded. + DataEndpoint string `protobuf:"bytes,1,opt,name=data_endpoint,json=dataEndpoint,proto3" json:"data_endpoint,omitempty"` + // The transfer token to be able to download the thumbnail. + TransferToken string `protobuf:"bytes,2,opt,name=transfer_token,json=transferToken,proto3" json:"transfer_token,omitempty"` // The mimetype of the thumbnail - Mimetype string `protobuf:"bytes,2,opt,name=mimetype,proto3" json:"mimetype,omitempty"` + Mimetype string `protobuf:"bytes,3,opt,name=mimetype,proto3" json:"mimetype,omitempty"` } func (x *GetThumbnailResponse) Reset() { @@ -233,11 +185,18 @@ func (*GetThumbnailResponse) Descriptor() ([]byte, []int) { return file_ocis_services_thumbnails_v0_thumbnails_proto_rawDescGZIP(), []int{1} } -func (x *GetThumbnailResponse) GetThumbnail() []byte { +func (x *GetThumbnailResponse) GetDataEndpoint() string { if x != nil { - return x.Thumbnail + return x.DataEndpoint } - return nil + return "" +} + +func (x *GetThumbnailResponse) GetTransferToken() string { + if x != nil { + return x.TransferToken + } + return "" } func (x *GetThumbnailResponse) GetMimetype() string { @@ -260,70 +219,69 @@ var file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc = []byte{ 0x69, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x03, 0x0a, 0x13, 0x47, 0x65, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd7, 0x02, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x65, 0x0a, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x70, 0x61, 0x74, 0x68, 0x12, 0x51, 0x0a, 0x0e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x3e, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, - 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x12, 0x50, 0x0a, 0x0d, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x63, 0x69, 0x73, - 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, - 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x2a, 0x0a, - 0x0d, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x07, - 0x0a, 0x03, 0x50, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x50, 0x47, 0x10, 0x01, - 0x12, 0x07, 0x0a, 0x03, 0x47, 0x49, 0x46, 0x10, 0x02, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x22, 0x50, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, - 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, - 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, - 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, - 0x65, 0x74, 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, - 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, - 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, + 0x2e, 0x76, 0x30, 0x2e, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0d, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x50, + 0x0a, 0x0d, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, + 0x2e, 0x76, 0x30, 0x2e, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x48, 0x00, 0x52, 0x0c, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x47, 0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, + 0x76, 0x30, 0x2e, 0x43, 0x53, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, + 0x63, 0x73, 0x33, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x42, 0x08, 0x0a, 0x06, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x22, 0x7e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, + 0x61, 0x74, 0x61, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, + 0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x74, + 0x79, 0x70, 0x65, 0x32, 0x87, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, + 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, + 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x30, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, + 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, - 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x6f, - 0x63, 0x69, 0x73, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x68, 0x75, - 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x68, - 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, - 0xeb, 0x02, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, - 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, - 0x6c, 0x73, 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, - 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, - 0x53, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, - 0x22, 0x47, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, - 0x48, 0x12, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, - 0x63, 0x69, 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, - 0x63, 0x68, 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, - 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, - 0x61, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, - 0x2e, 0x30, 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, - 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, - 0x12, 0x2b, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xeb, 0x02, + 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, + 0x2f, 0x76, 0x30, 0x92, 0x41, 0xa4, 0x02, 0x12, 0xb8, 0x01, 0x0a, 0x22, 0x6f, 0x77, 0x6e, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x49, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65, 0x20, 0x53, 0x63, + 0x61, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x47, + 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x20, 0x47, 0x6d, 0x62, 0x48, 0x12, + 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, + 0x73, 0x1a, 0x14, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x40, 0x6f, 0x77, 0x6e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x63, 0x6f, 0x6d, 0x2a, 0x42, 0x0a, 0x0a, 0x41, 0x70, 0x61, 0x63, 0x68, + 0x65, 0x2d, 0x32, 0x2e, 0x30, 0x12, 0x34, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2f, 0x6f, 0x63, 0x69, 0x73, 0x2f, 0x62, 0x6c, 0x6f, 0x62, 0x2f, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0x2f, 0x4c, 0x49, 0x43, 0x45, 0x4e, 0x53, 0x45, 0x32, 0x05, 0x31, 0x2e, 0x30, + 0x2e, 0x30, 0x2a, 0x02, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x72, 0x3f, 0x0a, 0x10, 0x44, 0x65, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x2b, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, + 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -338,21 +296,20 @@ func file_ocis_services_thumbnails_v0_thumbnails_proto_rawDescGZIP() []byte { return file_ocis_services_thumbnails_v0_thumbnails_proto_rawDescData } -var file_ocis_services_thumbnails_v0_thumbnails_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_ocis_services_thumbnails_v0_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_ocis_services_thumbnails_v0_thumbnails_proto_goTypes = []interface{}{ - (GetThumbnailRequest_ThumbnailType)(0), // 0: ocis.services.thumbnails.v0.GetThumbnailRequest.ThumbnailType - (*GetThumbnailRequest)(nil), // 1: ocis.services.thumbnails.v0.GetThumbnailRequest - (*GetThumbnailResponse)(nil), // 2: ocis.services.thumbnails.v0.GetThumbnailResponse - (*v0.WebdavSource)(nil), // 3: ocis.messages.thumbnails.v0.WebdavSource - (*v0.CS3Source)(nil), // 4: ocis.messages.thumbnails.v0.CS3Source + (*GetThumbnailRequest)(nil), // 0: ocis.services.thumbnails.v0.GetThumbnailRequest + (*GetThumbnailResponse)(nil), // 1: ocis.services.thumbnails.v0.GetThumbnailResponse + (v0.ThumbnailType)(0), // 2: ocis.messages.thumbnails.v0.ThumbnailType + (*v0.WebdavSource)(nil), // 3: ocis.messages.thumbnails.v0.WebdavSource + (*v0.CS3Source)(nil), // 4: ocis.messages.thumbnails.v0.CS3Source } var file_ocis_services_thumbnails_v0_thumbnails_proto_depIdxs = []int32{ - 0, // 0: ocis.services.thumbnails.v0.GetThumbnailRequest.thumbnail_type:type_name -> ocis.services.thumbnails.v0.GetThumbnailRequest.ThumbnailType + 2, // 0: ocis.services.thumbnails.v0.GetThumbnailRequest.thumbnail_type:type_name -> ocis.messages.thumbnails.v0.ThumbnailType 3, // 1: ocis.services.thumbnails.v0.GetThumbnailRequest.webdav_source:type_name -> ocis.messages.thumbnails.v0.WebdavSource 4, // 2: ocis.services.thumbnails.v0.GetThumbnailRequest.cs3_source:type_name -> ocis.messages.thumbnails.v0.CS3Source - 1, // 3: ocis.services.thumbnails.v0.ThumbnailService.GetThumbnail:input_type -> ocis.services.thumbnails.v0.GetThumbnailRequest - 2, // 4: ocis.services.thumbnails.v0.ThumbnailService.GetThumbnail:output_type -> ocis.services.thumbnails.v0.GetThumbnailResponse + 0, // 3: ocis.services.thumbnails.v0.ThumbnailService.GetThumbnail:input_type -> ocis.services.thumbnails.v0.GetThumbnailRequest + 1, // 4: ocis.services.thumbnails.v0.ThumbnailService.GetThumbnail:output_type -> ocis.services.thumbnails.v0.GetThumbnailResponse 4, // [4:5] is the sub-list for method output_type 3, // [3:4] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name @@ -400,14 +357,13 @@ func file_ocis_services_thumbnails_v0_thumbnails_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ocis_services_thumbnails_v0_thumbnails_proto_rawDesc, - NumEnums: 1, + NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, GoTypes: file_ocis_services_thumbnails_v0_thumbnails_proto_goTypes, DependencyIndexes: file_ocis_services_thumbnails_v0_thumbnails_proto_depIdxs, - EnumInfos: file_ocis_services_thumbnails_v0_thumbnails_proto_enumTypes, MessageInfos: file_ocis_services_thumbnails_v0_thumbnails_proto_msgTypes, }.Build() File_ocis_services_thumbnails_v0_thumbnails_proto = out.File diff --git a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json index aef36cc0953..7e4731e46d7 100644 --- a/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json +++ b/protogen/gen/ocis/services/thumbnails/v0/thumbnails.swagger.json @@ -30,16 +30,6 @@ ], "paths": {}, "definitions": { - "GetThumbnailRequestThumbnailType": { - "type": "string", - "enum": [ - "PNG", - "JPG", - "GIF" - ], - "default": "PNG", - "description": "The file types to which the thumbnail can get encoded to." - }, "protobufAny": { "type": "object", "properties": { @@ -81,10 +71,13 @@ "v0GetThumbnailResponse": { "type": "object", "properties": { - "thumbnail": { + "dataEndpoint": { + "type": "string", + "description": "The endpoint where the thumbnail can be downloaded." + }, + "transferToken": { "type": "string", - "format": "byte", - "title": "The thumbnail as a binary" + "description": "The transfer token to be able to download the thumbnail." }, "mimetype": { "type": "string", @@ -93,6 +86,16 @@ }, "title": "The service response" }, + "v0ThumbnailType": { + "type": "string", + "enum": [ + "PNG", + "JPG", + "GIF" + ], + "default": "PNG", + "description": "The file types to which the thumbnail can be encoded to." + }, "v0WebdavSource": { "type": "object", "properties": { diff --git a/protogen/proto/ocis/messages/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/messages/thumbnails/v0/thumbnails.proto index 3964970ef87..d5c0d8a77bd 100644 --- a/protogen/proto/ocis/messages/thumbnails/v0/thumbnails.proto +++ b/protogen/proto/ocis/messages/thumbnails/v0/thumbnails.proto @@ -21,3 +21,10 @@ message CS3Source { string path = 1; string authorization = 2; } + +// The file types to which the thumbnail can be encoded to. +enum ThumbnailType { + PNG = 0; // Represents PNG type + JPG = 1; // Represents JPG type + GIF = 2; // Represents GIF type +} \ No newline at end of file diff --git a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto index c8141aedba3..148175586d2 100644 --- a/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto +++ b/protogen/proto/ocis/services/thumbnails/v0/thumbnails.proto @@ -41,14 +41,8 @@ service ThumbnailService { message GetThumbnailRequest { // The path to the source image string filepath = 1; - // The file types to which the thumbnail can get encoded to. - enum ThumbnailType { - PNG = 0; // Represents PNG type - JPG = 1; // Represents JPG type - GIF = 2; // Represents GIF type - } // The type to which the thumbnail should get encoded to. - ThumbnailType thumbnail_type = 2; + ocis.messages.thumbnails.v0.ThumbnailType thumbnail_type = 2; // The width of the thumbnail int32 width = 3; // The height of the thumbnail @@ -61,8 +55,10 @@ message GetThumbnailRequest { // The service response message GetThumbnailResponse { - // The thumbnail as a binary - bytes thumbnail = 1; + // The endpoint where the thumbnail can be downloaded. + string data_endpoint = 1; + // The transfer token to be able to download the thumbnail. + string transfer_token = 2; // The mimetype of the thumbnail - string mimetype = 2; + string mimetype = 3; } diff --git a/thumbnails/pkg/command/server.go b/thumbnails/pkg/command/server.go index f44b230d36c..86fb9d91a79 100644 --- a/thumbnails/pkg/command/server.go +++ b/thumbnails/pkg/command/server.go @@ -12,6 +12,7 @@ import ( "github.com/owncloud/ocis/thumbnails/pkg/metrics" "github.com/owncloud/ocis/thumbnails/pkg/server/debug" "github.com/owncloud/ocis/thumbnails/pkg/server/grpc" + "github.com/owncloud/ocis/thumbnails/pkg/server/http" "github.com/owncloud/ocis/thumbnails/pkg/tracing" "github.com/urfave/cli/v2" ) @@ -57,9 +58,7 @@ func Server(cfg *config.Config) *cli.Command { grpc.Metrics(metrics), ) - gr.Add(func() error { - return service.Run() - }, func(_ error) { + gr.Add(service.Run, func(_ error) { fmt.Println("shutting down grpc server") cancel() }) @@ -79,6 +78,28 @@ func Server(cfg *config.Config) *cli.Command { cancel() }) + httpServer, err := http.Server( + http.Logger(logger), + http.Context(ctx), + http.Config(cfg), + http.Metrics(metrics), + http.Namespace(cfg.HTTP.Namespace), + ) + + if err != nil { + logger.Info(). + Err(err). + Str("transport", "http"). + Msg("Failed to initialize server") + + return err + } + + gr.Add(httpServer.Run, func(_ error) { + logger.Info().Str("server", "http").Msg("shutting down server") + cancel() + }) + return gr.Run() }, } diff --git a/thumbnails/pkg/config/config.go b/thumbnails/pkg/config/config.go index 527f94a50e7..2311a4e4dd0 100644 --- a/thumbnails/pkg/config/config.go +++ b/thumbnails/pkg/config/config.go @@ -17,6 +17,7 @@ type Config struct { Debug Debug `ocisConfig:"debug"` GRPC GRPC `ocisConfig:"grpc"` + HTTP HTTP `ocisConfig:"http"` Thumbnail Thumbnail `ocisConfig:"thumbnail"` @@ -41,4 +42,6 @@ type Thumbnail struct { CS3AllowInsecure bool `ocisConfig:"cs3_allow_insecure" env:"OCIS_INSECURE;THUMBNAILS_CS3SOURCE_INSECURE"` RevaGateway string `ocisConfig:"reva_gateway" env:"REVA_GATEWAY"` //TODO: use REVA config FontMapFile string `ocisConfig:"font_map_file" env:"THUMBNAILS_TXT_FONTMAP_FILE"` + TransferTokenSecret string `ocisConfig:"transfer_token" env:"THUMBNAILS_TRANSFER_TOKEN"` + DataEndpoint string `ocisConfig:"data_endpoint" env:"THUMBNAILS_DATA_ENDPOINT"` } diff --git a/thumbnails/pkg/config/defaultconfig.go b/thumbnails/pkg/config/defaultconfig.go index 8f59db53e3d..5e4a3e42323 100644 --- a/thumbnails/pkg/config/defaultconfig.go +++ b/thumbnails/pkg/config/defaultconfig.go @@ -18,6 +18,11 @@ func DefaultConfig() *Config { Addr: "127.0.0.1:9185", Namespace: "com.owncloud.api", }, + HTTP: HTTP{ + Addr: "127.0.0.1:9186", + Root: "/thumbnails", + Namespace: "com.owncloud.web", + }, Service: Service{ Name: "thumbnails", }, @@ -29,6 +34,8 @@ func DefaultConfig() *Config { WebdavAllowInsecure: true, RevaGateway: "127.0.0.1:9142", CS3AllowInsecure: false, + TransferTokenSecret: "changemeplease", + DataEndpoint: "http://127.0.0.1:9186/thumbnails/data", }, } } diff --git a/thumbnails/pkg/config/http.go b/thumbnails/pkg/config/http.go new file mode 100644 index 00000000000..f3b4d572845 --- /dev/null +++ b/thumbnails/pkg/config/http.go @@ -0,0 +1,8 @@ +package config + +// HTTP defines the available http configuration. +type HTTP struct { + Addr string `ocisConfig:"addr" env:"THUMBNAILS_HTTP_ADDR"` + Root string `ocisConfig:"root" env:"THUMBNAILS_HTTP_ROOT"` + Namespace string +} diff --git a/thumbnails/pkg/server/grpc/server.go b/thumbnails/pkg/server/grpc/server.go index 825d8088149..f4e11e451bc 100644 --- a/thumbnails/pkg/server/grpc/server.go +++ b/thumbnails/pkg/server/grpc/server.go @@ -5,8 +5,8 @@ import ( "github.com/owncloud/ocis/ocis-pkg/service/grpc" "github.com/owncloud/ocis/ocis-pkg/version" thumbnailssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/thumbnails/v0" - svc "github.com/owncloud/ocis/thumbnails/pkg/service/v0" - "github.com/owncloud/ocis/thumbnails/pkg/service/v0/decorators" + svc "github.com/owncloud/ocis/thumbnails/pkg/service/grpc/v0" + "github.com/owncloud/ocis/thumbnails/pkg/service/grpc/v0/decorators" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/imgsource" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" ) diff --git a/thumbnails/pkg/server/http/option.go b/thumbnails/pkg/server/http/option.go new file mode 100644 index 00000000000..9f1fa828e23 --- /dev/null +++ b/thumbnails/pkg/server/http/option.go @@ -0,0 +1,69 @@ +package http + +import ( + "context" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/owncloud/ocis/thumbnails/pkg/metrics" + "github.com/urfave/cli/v2" +) + +// Option defines a single option function. +type Option func(o *Options) + +// Options defines the available options for this package. +type Options struct { + Namespace string + Logger log.Logger + Context context.Context + Config *config.Config + Metrics *metrics.Metrics + Flags []cli.Flag +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Context provides a function to set the context option. +func Context(val context.Context) Option { + return func(o *Options) { + o.Context = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Metrics provides a function to set the metrics option. +func Metrics(val *metrics.Metrics) Option { + return func(o *Options) { + o.Metrics = val + } +} + +// Namespace provides a function to set the Namespace option. +func Namespace(val string) Option { + return func(o *Options) { + o.Namespace = val + } +} diff --git a/thumbnails/pkg/server/http/server.go b/thumbnails/pkg/server/http/server.go new file mode 100644 index 00000000000..10d013a3498 --- /dev/null +++ b/thumbnails/pkg/server/http/server.go @@ -0,0 +1,58 @@ +package http + +import ( + "github.com/go-chi/chi/v5/middleware" + ocismiddleware "github.com/owncloud/ocis/ocis-pkg/middleware" + "github.com/owncloud/ocis/ocis-pkg/service/http" + "github.com/owncloud/ocis/ocis-pkg/version" + svc "github.com/owncloud/ocis/thumbnails/pkg/service/http/v0" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" + "go-micro.dev/v4" +) + +// Server initializes the http service and server. +func Server(opts ...Option) (http.Service, error) { + options := newOptions(opts...) + + service := http.NewService( + http.Logger(options.Logger), + http.Name(options.Config.Service.Name), + http.Version(version.String), + http.Namespace(options.Config.HTTP.Namespace), + http.Address(options.Config.HTTP.Addr), + http.Context(options.Context), + ) + + handle := svc.NewService( + svc.Logger(options.Logger), + svc.Config(options.Config), + svc.Middleware( + middleware.RealIP, + middleware.RequestID, + // ocismiddleware.Secure, + ocismiddleware.Version( + options.Config.Service.Name, + version.String, + ), + ocismiddleware.Logger(options.Logger), + ), + svc.ThumbnailStorage( + storage.NewFileSystemStorage( + options.Config.Thumbnail.FileSystemStorage, + options.Logger, + ), + ), + ) + + { + handle = svc.NewInstrument(handle, options.Metrics) + handle = svc.NewLogging(handle, options.Logger) + handle = svc.NewTracing(handle) + } + + if err := micro.RegisterHandler(service.Server(), handle); err != nil { + return http.Service{}, err + } + + return service, nil +} diff --git a/thumbnails/pkg/service/v0/decorators/base.go b/thumbnails/pkg/service/grpc/v0/decorators/base.go similarity index 100% rename from thumbnails/pkg/service/v0/decorators/base.go rename to thumbnails/pkg/service/grpc/v0/decorators/base.go diff --git a/thumbnails/pkg/service/v0/decorators/instrument.go b/thumbnails/pkg/service/grpc/v0/decorators/instrument.go similarity index 100% rename from thumbnails/pkg/service/v0/decorators/instrument.go rename to thumbnails/pkg/service/grpc/v0/decorators/instrument.go diff --git a/thumbnails/pkg/service/v0/decorators/logging.go b/thumbnails/pkg/service/grpc/v0/decorators/logging.go similarity index 100% rename from thumbnails/pkg/service/v0/decorators/logging.go rename to thumbnails/pkg/service/grpc/v0/decorators/logging.go diff --git a/thumbnails/pkg/service/v0/decorators/tracing.go b/thumbnails/pkg/service/grpc/v0/decorators/tracing.go similarity index 100% rename from thumbnails/pkg/service/v0/decorators/tracing.go rename to thumbnails/pkg/service/grpc/v0/decorators/tracing.go diff --git a/thumbnails/pkg/service/v0/option.go b/thumbnails/pkg/service/grpc/v0/option.go similarity index 100% rename from thumbnails/pkg/service/v0/option.go rename to thumbnails/pkg/service/grpc/v0/option.go diff --git a/thumbnails/pkg/service/v0/service.go b/thumbnails/pkg/service/grpc/v0/service.go similarity index 67% rename from thumbnails/pkg/service/v0/service.go rename to thumbnails/pkg/service/grpc/v0/service.go index 32f9688ceeb..ef1b16b87a2 100644 --- a/thumbnails/pkg/service/v0/service.go +++ b/thumbnails/pkg/service/grpc/v0/service.go @@ -6,15 +6,19 @@ import ( "net/url" "path" "strings" + "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/v2/pkg/ctx" + "github.com/golang-jwt/jwt/v4" "github.com/owncloud/ocis/ocis-pkg/log" + thumbnailsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/thumbnails/v0" thumbnailssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/thumbnails/v0" "github.com/owncloud/ocis/thumbnails/pkg/preprocessor" - "github.com/owncloud/ocis/thumbnails/pkg/service/v0/decorators" + "github.com/owncloud/ocis/thumbnails/pkg/service/grpc/v0/decorators" + tjwt "github.com/owncloud/ocis/thumbnails/pkg/service/jwt" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/imgsource" "github.com/pkg/errors" @@ -44,6 +48,8 @@ func NewService(opts ...Option) decorators.DecoratedService { preprocessorOpts: PreprocessorOpts{ TxtFontFileMap: options.Config.Thumbnail.FontMapFile, }, + dataEndpoint: options.Config.Thumbnail.DataEndpoint, + transferTokenSecret: options.Config.Thumbnail.TransferTokenSecret, } return svc @@ -51,13 +57,15 @@ func NewService(opts ...Option) decorators.DecoratedService { // Thumbnail implements the GRPC handler. type Thumbnail struct { - serviceID string - manager thumbnail.Manager - webdavSource imgsource.Source - cs3Source imgsource.Source - logger log.Logger - cs3Client gateway.GatewayAPIClient - preprocessorOpts PreprocessorOpts + serviceID string + dataEndpoint string + transferTokenSecret string + manager thumbnail.Manager + webdavSource imgsource.Source + cs3Source imgsource.Source + logger log.Logger + cs3Client gateway.GatewayAPIClient + preprocessorOpts PreprocessorOpts } type PreprocessorOpts struct { @@ -66,28 +74,28 @@ type PreprocessorOpts struct { // GetThumbnail retrieves a thumbnail for an image func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, rsp *thumbnailssvc.GetThumbnailResponse) error { - _, ok := thumbnailssvc.GetThumbnailRequest_ThumbnailType_value[req.ThumbnailType.String()] + tType, ok := thumbnailsmsg.ThumbnailType_name[int32(req.ThumbnailType)] if !ok { - g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") return nil } - generator, err := thumbnail.GeneratorForType(req.ThumbnailType.String()) + generator, err := thumbnail.GeneratorForType(tType) if err != nil { - g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") return nil } - encoder, err := thumbnail.EncoderForType(req.ThumbnailType.String()) + encoder, err := thumbnail.EncoderForType(tType) if err != nil { - g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type") + g.logger.Debug().Str("thumbnail_type", tType).Msg("unsupported thumbnail type") return nil } - var thumb []byte + var key string switch { case req.GetWebdavSource() != nil: - thumb, err = g.handleWebdavSource(ctx, req, generator, encoder) + key, err = g.handleWebdavSource(ctx, req, generator, encoder) case req.GetCs3Source() != nil: - thumb, err = g.handleCS3Source(ctx, req, generator, encoder) + key, err = g.handleCS3Source(ctx, req, generator, encoder) default: g.logger.Error().Msg("no image source provided") return merrors.BadRequest(g.serviceID, "image source is missing") @@ -96,16 +104,36 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *thumbnailssvc.GetThumb return err } - rsp.Thumbnail = thumb + claims := tjwt.ThumbnailClaims{ + Key: key, + RegisteredClaims: jwt.RegisteredClaims{ + IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Minute)), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + transferToken, err := token.SignedString([]byte(g.transferTokenSecret)) + if err != nil { + g.logger.Error(). + Err(err). + Msg("GetThumbnail: failed to sign token") + return merrors.InternalServerError(g.serviceID, "couldn't finish request") + } + rsp.DataEndpoint = g.dataEndpoint + rsp.TransferToken = transferToken rsp.Mimetype = encoder.MimeType() return nil } -func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleCS3Source(ctx context.Context, + req *thumbnailssvc.GetThumbnailRequest, + generator thumbnail.Generator, + encoder thumbnail.Encoder) (string, error) { src := req.GetCs3Source() sRes, err := g.stat(src.Path, src.Authorization) if err != nil { - return nil, err + return "", err } tr := thumbnail.Request{ @@ -115,15 +143,14 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh Checksum: sRes.GetInfo().GetChecksum().GetSum(), } - thumb, ok := g.manager.Get(tr) - if ok { - return thumb, nil + if key, exists := g.manager.CheckThumbnail(tr); exists { + return key, nil } ctx = imgsource.ContextSetAuthorization(ctx, src.Authorization) r, err := g.cs3Source.Get(ctx, src.Path) if err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) + return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) } defer r.Close() // nolint:errcheck ppOpts := map[string]interface{}{ @@ -132,20 +159,24 @@ func (g Thumbnail) handleCS3Source(ctx context.Context, req *thumbnailssvc.GetTh pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) img, err := pp.Convert(r) if img == nil || err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not get image") - } - if thumb, err = g.manager.Generate(tr, img); err != nil { - return nil, err + return "", merrors.InternalServerError(g.serviceID, "could not get image") } - return thumb, nil + key, err := g.manager.Generate(tr, img) + if err != nil { + return "", err + } + return key, nil } -func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.GetThumbnailRequest, generator thumbnail.Generator, encoder thumbnail.Encoder) ([]byte, error) { +func (g Thumbnail) handleWebdavSource(ctx context.Context, + req *thumbnailssvc.GetThumbnailRequest, + generator thumbnail.Generator, + encoder thumbnail.Encoder) (string, error) { src := req.GetWebdavSource() imgURL, err := url.Parse(src.Url) if err != nil { - return nil, errors.Wrap(err, "source url is invalid") + return "", errors.Wrap(err, "source url is invalid") } var auth, statPath string @@ -173,7 +204,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge } if err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error()) + return "", merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error()) } auth = rsp.Token statPath = path.Join("/public", src.PublicLinkToken, req.Filepath) @@ -183,7 +214,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge } sRes, err := g.stat(statPath, auth) if err != nil { - return nil, err + return "", err } tr := thumbnail.Request{ Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)), @@ -191,9 +222,9 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge Encoder: encoder, Checksum: sRes.GetInfo().GetChecksum().GetSum(), } - thumb, ok := g.manager.Get(tr) - if ok { - return thumb, nil + + if key, exists := g.manager.CheckThumbnail(tr); exists { + return key, nil } if src.WebdavAuthorization != "" { @@ -202,7 +233,7 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge imgURL.RawQuery = "" r, err := g.webdavSource.Get(ctx, imgURL.String()) if err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) + return "", merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error()) } defer r.Close() // nolint:errcheck ppOpts := map[string]interface{}{ @@ -211,13 +242,14 @@ func (g Thumbnail) handleWebdavSource(ctx context.Context, req *thumbnailssvc.Ge pp := preprocessor.ForType(sRes.GetInfo().GetMimeType(), ppOpts) img, err := pp.Convert(r) if img == nil || err != nil { - return nil, merrors.InternalServerError(g.serviceID, "could not get image") - } - if thumb, err = g.manager.Generate(tr, img); err != nil { - return nil, err + return "", merrors.InternalServerError(g.serviceID, "could not get image") } - return thumb, nil + key, err := g.manager.Generate(tr, img) + if err != nil { + return "", err + } + return key, nil } func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) { diff --git a/thumbnails/pkg/service/http/v0/instrument.go b/thumbnails/pkg/service/http/v0/instrument.go new file mode 100644 index 00000000000..baab1c6fee8 --- /dev/null +++ b/thumbnails/pkg/service/http/v0/instrument.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/thumbnails/pkg/metrics" +) + +// NewInstrument returns a service that instruments metrics. +func NewInstrument(next Service, metrics *metrics.Metrics) Service { + return instrument{ + next: next, + metrics: metrics, + } +} + +type instrument struct { + next Service + metrics *metrics.Metrics +} + +// ServeHTTP implements the Service interface. +func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) { + i.next.ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (i instrument) GetThumbnail(w http.ResponseWriter, r *http.Request) { + i.next.GetThumbnail(w, r) +} diff --git a/thumbnails/pkg/service/http/v0/logging.go b/thumbnails/pkg/service/http/v0/logging.go new file mode 100644 index 00000000000..84701b1b072 --- /dev/null +++ b/thumbnails/pkg/service/http/v0/logging.go @@ -0,0 +1,30 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/ocis-pkg/log" +) + +// NewLogging returns a service that logs messages. +func NewLogging(next Service, logger log.Logger) Service { + return logging{ + next: next, + logger: logger, + } +} + +type logging struct { + next Service + logger log.Logger +} + +// ServeHTTP implements the Service interface. +func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) { + l.next.ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (l logging) GetThumbnail(w http.ResponseWriter, r *http.Request) { + l.next.GetThumbnail(w, r) +} diff --git a/thumbnails/pkg/service/http/v0/option.go b/thumbnails/pkg/service/http/v0/option.go new file mode 100644 index 00000000000..efcf65f358f --- /dev/null +++ b/thumbnails/pkg/service/http/v0/option.go @@ -0,0 +1,59 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" +) + +// Option defines a single option function. +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 + ThumbnailStorage storage.Storage +} + +// newOptions initializes the available default options. +func newOptions(opts ...Option) Options { + opt := Options{} + + for _, o := range opts { + o(&opt) + } + + return opt +} + +// Logger provides a function to set the logger option. +func Logger(val log.Logger) Option { + return func(o *Options) { + o.Logger = val + } +} + +// Config provides a function to set the config option. +func Config(val *config.Config) Option { + return func(o *Options) { + o.Config = val + } +} + +// Middleware provides a function to set the middleware option. +func Middleware(val ...func(http.Handler) http.Handler) Option { + return func(o *Options) { + o.Middleware = val + } +} + +// ThumbnailStorage provides a function to set the ThumbnailStorage option. +func ThumbnailStorage(storage storage.Storage) Option { + return func(o *Options) { + o.ThumbnailStorage = storage + } +} diff --git a/thumbnails/pkg/service/http/v0/service.go b/thumbnails/pkg/service/http/v0/service.go new file mode 100644 index 00000000000..76df357a23e --- /dev/null +++ b/thumbnails/pkg/service/http/v0/service.go @@ -0,0 +1,123 @@ +package svc + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + "github.com/golang-jwt/jwt/v4" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/config" + tjwt "github.com/owncloud/ocis/thumbnails/pkg/service/jwt" + "github.com/owncloud/ocis/thumbnails/pkg/thumbnail" +) + +type contextKey string + +const ( + keyContextKey contextKey = "key" +) + +// Service defines the extension handlers. +type Service interface { + ServeHTTP(http.ResponseWriter, *http.Request) + GetThumbnail(http.ResponseWriter, *http.Request) +} + +// NewService returns a service implementation for Service. +func NewService(opts ...Option) Service { + options := newOptions(opts...) + + m := chi.NewMux() + m.Use(options.Middleware...) + + logger := options.Logger + resolutions, err := thumbnail.ParseResolutions(options.Config.Thumbnail.Resolutions) + if err != nil { + logger.Fatal().Err(err).Msg("resolutions not configured correctly") + } + svc := Thumbnails{ + config: options.Config, + mux: m, + logger: options.Logger, + manager: thumbnail.NewSimpleManager( + resolutions, + options.ThumbnailStorage, + logger, + ), + } + + m.Route(options.Config.HTTP.Root, func(r chi.Router) { + r.Use(svc.TransferTokenValidator) + r.Get("/data", svc.GetThumbnail) + }) + + return svc +} + +// Thumbnails implements the business logic for Service. +type Thumbnails struct { + config *config.Config + logger log.Logger + mux *chi.Mux + manager thumbnail.Manager +} + +// ServeHTTP implements the Service interface. +func (s Thumbnails) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.mux.ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (s Thumbnails) GetThumbnail(w http.ResponseWriter, r *http.Request) { + key := r.Context().Value(keyContextKey).(string) + + thumbnail, err := s.manager.GetThumbnail(key) + if err != nil { + s.logger.Error(). + Err(err). + Str("key", key). + Msg("could not get the thumbnail") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Length", strconv.Itoa(len(thumbnail))) + if _, err = w.Write(thumbnail); err != nil { + s.logger.Error(). + Err(err). + Str("key", key). + Msg("could not write the thumbnail response") + } +} + +func (s Thumbnails) TransferTokenValidator(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + tokenString := r.Header.Get("Transfer-Token") + token, err := jwt.ParseWithClaims(tokenString, &tjwt.ThumbnailClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(s.config.Thumbnail.TransferTokenSecret), nil + }) + if err != nil { + s.logger.Error(). + Err(err). + Str("transfer-token", tokenString). + Msg("failed to parse transfer token") + w.WriteHeader(http.StatusUnauthorized) + return + } + + if claims, ok := token.Claims.(*tjwt.ThumbnailClaims); ok && token.Valid { + ctx := context.WithValue(r.Context(), keyContextKey, claims.Key) + next.ServeHTTP(w, r.WithContext(ctx)) + return + } + w.WriteHeader(http.StatusUnauthorized) + }) +} diff --git a/thumbnails/pkg/service/http/v0/tracing.go b/thumbnails/pkg/service/http/v0/tracing.go new file mode 100644 index 00000000000..ce9a01d0f03 --- /dev/null +++ b/thumbnails/pkg/service/http/v0/tracing.go @@ -0,0 +1,28 @@ +package svc + +import ( + "net/http" + + "github.com/owncloud/ocis/ocis-pkg/middleware" +) + +// NewTracing returns a service that instruments traces. +func NewTracing(next Service) Service { + return tracing{ + next: next, + } +} + +type tracing struct { + next Service +} + +// ServeHTTP implements the Service interface. +func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) { + middleware.TraceContext(t.next).ServeHTTP(w, r) +} + +// GetThumbnail implements the Service interface. +func (t tracing) GetThumbnail(w http.ResponseWriter, r *http.Request) { + t.next.GetThumbnail(w, r) +} diff --git a/thumbnails/pkg/service/jwt/jwt.go b/thumbnails/pkg/service/jwt/jwt.go new file mode 100644 index 00000000000..bbe46bb94d3 --- /dev/null +++ b/thumbnails/pkg/service/jwt/jwt.go @@ -0,0 +1,8 @@ +package jwt + +import "github.com/golang-jwt/jwt/v4" + +type ThumbnailClaims struct { + jwt.RegisteredClaims + Key string `json:"key"` +} diff --git a/thumbnails/pkg/thumbnail/storage/filesystem.go b/thumbnails/pkg/thumbnail/storage/filesystem.go index 604ca13c55e..980adedd32b 100644 --- a/thumbnails/pkg/thumbnail/storage/filesystem.go +++ b/thumbnails/pkg/thumbnail/storage/filesystem.go @@ -1,12 +1,14 @@ package storage import ( - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/thumbnails/pkg/config" - "github.com/pkg/errors" + "io/fs" "os" "path/filepath" "strconv" + + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/thumbnails/pkg/config" + "github.com/pkg/errors" ) const ( @@ -14,8 +16,8 @@ const ( ) // NewFileSystemStorage creates a new instance of FileSystem -func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) *FileSystem { - return &FileSystem{ +func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) FileSystem { + return FileSystem{ root: cfg.RootDirectory, logger: logger, } @@ -27,21 +29,27 @@ type FileSystem struct { logger log.Logger } -// Get loads the image from the file system. -func (s *FileSystem) Get(key string) ([]byte, bool) { +func (s FileSystem) Stat(key string) bool { + img := filepath.Join(s.root, filesDir, key) + if _, err := os.Stat(img); err != nil { + return false + } + return true +} + +func (s FileSystem) Get(key string) ([]byte, error) { img := filepath.Join(s.root, filesDir, key) content, err := os.ReadFile(img) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { s.logger.Debug().Str("err", err.Error()).Str("key", key).Msg("could not load thumbnail from store") } - return nil, false + return nil, err } - return content, true + return content, nil } -// Set writes the image to the file system. -func (s *FileSystem) Put(key string, img []byte) error { +func (s FileSystem) Put(key string, img []byte) error { imgPath := filepath.Join(s.root, filesDir, key) dir := filepath.Dir(imgPath) if err := os.MkdirAll(dir, 0700); err != nil { @@ -71,7 +79,7 @@ func (s *FileSystem) Put(key string, img []byte) error { // e.g. 97/9f/4c8db98f7b82e768ef478d3c8612/500x300.png // // The key also represents the path to the thumbnail in the filesystem under the configured root directory. -func (s *FileSystem) BuildKey(r Request) string { +func (s FileSystem) BuildKey(r Request) string { checksum := r.Checksum filetype := r.Types[0] filename := strconv.Itoa(r.Resolution.Dx()) + "x" + strconv.Itoa(r.Resolution.Dy()) + "." + filetype diff --git a/thumbnails/pkg/thumbnail/storage/inmemory.go b/thumbnails/pkg/thumbnail/storage/inmemory.go index 7ec358ca420..1c7befd888e 100644 --- a/thumbnails/pkg/thumbnail/storage/inmemory.go +++ b/thumbnails/pkg/thumbnail/storage/inmemory.go @@ -17,9 +17,14 @@ type InMemory struct { store map[string][]byte } +func (s InMemory) Stat(key string) bool { + _, exists := s.store[key] + return exists +} + // Get loads the thumbnail from memory. -func (s InMemory) Get(key string) ([]byte, bool) { - return s.store[key], true +func (s InMemory) Get(key string) ([]byte, error) { + return s.store[key], nil } // Set stores the thumbnail in memory. diff --git a/thumbnails/pkg/thumbnail/storage/storage.go b/thumbnails/pkg/thumbnail/storage/storage.go index bf7f3c54546..691fdca5af3 100644 --- a/thumbnails/pkg/thumbnail/storage/storage.go +++ b/thumbnails/pkg/thumbnail/storage/storage.go @@ -8,18 +8,19 @@ import ( type Request struct { // The checksum of the source file // Will be used to determine if a thumbnail exists - Checksum string + Checksum string // Types provided by the encoder. // Contains the mimetypes of the thumbnail. // In case of jpg/jpeg it will contain both. - Types []string + Types []string // The resolution of the thumbnail Resolution image.Rectangle } // Storage defines the interface for a thumbnail store. type Storage interface { - Get(string) ([]byte, bool) + Stat(string) bool + Get(string) ([]byte, error) Put(string, []byte) error BuildKey(Request) string } diff --git a/thumbnails/pkg/thumbnail/thumbnail.go b/thumbnails/pkg/thumbnail/thumbnail.go index ceeca03f410..b4f2f7b80c5 100644 --- a/thumbnails/pkg/thumbnail/thumbnail.go +++ b/thumbnails/pkg/thumbnail/thumbnail.go @@ -5,19 +5,19 @@ import ( "image" "image/gif" "mime" - "strings" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage" ) var ( - SupportedMimeTypes = [...]string{ - "image/png", - "image/jpg", - "image/jpeg", - "image/gif", - "text/plain", + // SupportedMimeTypes contains a all mimetypes which are supported by the thumbnailer. + SupportedMimeTypes = map[string]struct{}{ + "image/png": {}, + "image/jpg": {}, + "image/jpeg": {}, + "image/gif": {}, + "text/plain": {}, } ) @@ -31,11 +31,14 @@ type Request struct { // Manager is responsible for generating thumbnails type Manager interface { - // Generate will return a thumbnail for a file - Generate(Request, interface{}) ([]byte, error) - // Get loads the thumbnail from the storage. - // It will return nil if no image is stored for the given context. - Get(Request) ([]byte, bool) + // Generate creates a thumbnail and stores it. + // The function returns a key with which the actual file can be retrieved. + Generate(Request, interface{}) (string, error) + // CheckThumbnail checks if a thumbnail with the requested attributes exists. + // The function will return a status if the file exists and the key to the file. + CheckThumbnail(Request) (string, bool) + // GetThumbnail will load the thumbnail from the storage and return its content. + GetThumbnail(key string) ([]byte, error) } // NewSimpleManager creates a new instance of SimpleManager @@ -54,9 +57,7 @@ type SimpleManager struct { resolutions Resolutions } -// Generate creates a thumbnail and stores it. -// The created thumbnail is also being returned. -func (s SimpleManager) Generate(r Request, img interface{}) ([]byte, error) { +func (s SimpleManager) Generate(r Request, img interface{}) (string, error) { var match image.Rectangle switch m := img.(type) { case *gif.GIF: @@ -67,28 +68,29 @@ func (s SimpleManager) Generate(r Request, img interface{}) ([]byte, error) { thumbnail, err := r.Generator.GenerateThumbnail(match, img) if err != nil { - return nil, err + return "", err } - dst := new(bytes.Buffer) - err = r.Encoder.Encode(dst, thumbnail) - if err != nil { - return nil, err + buf := new(bytes.Buffer) + if err := r.Encoder.Encode(buf, thumbnail); err != nil { + return "", err } k := s.storage.BuildKey(mapToStorageRequest(r)) - err = s.storage.Put(k, dst.Bytes()) - if err != nil { - s.logger.Warn().Err(err).Msg("could not store thumbnail") + if err := s.storage.Put(k, buf.Bytes()); err != nil { + s.logger.Error().Err(err).Msg("could not store thumbnail") + return "", err } - return dst.Bytes(), nil + return k, nil } -// Get tries to get the stored thumbnail and return it. -// If there is no cached thumbnail it will return nil -func (s SimpleManager) Get(r Request) ([]byte, bool) { +func (s SimpleManager) CheckThumbnail(r Request) (string, bool) { k := s.storage.BuildKey(mapToStorageRequest(r)) - return s.storage.Get(k) + return k, s.storage.Stat(k) +} + +func (s SimpleManager) GetThumbnail(key string) ([]byte, error) { + return s.storage.Get(key) } func mapToStorageRequest(r Request) storage.Request { @@ -104,10 +106,6 @@ func IsMimeTypeSupported(m string) bool { if err != nil { return false } - for _, mt := range SupportedMimeTypes { - if strings.EqualFold(mt, mimeType) { - return true - } - } - return false + _, supported := SupportedMimeTypes[mimeType] + return supported } diff --git a/webdav/pkg/service/v0/service.go b/webdav/pkg/service/v0/service.go index c1d92011d5a..c98a3b3afd0 100644 --- a/webdav/pkg/service/v0/service.go +++ b/webdav/pkg/service/v0/service.go @@ -2,6 +2,7 @@ package svc import ( "encoding/xml" + "io" "net/http" "path/filepath" "strings" @@ -128,12 +129,7 @@ func (g Webdav) SpacesThumbnail(w http.ResponseWriter, r *http.Request) { return } - if len(rsp.Thumbnail) == 0 { - renderError(w, r, errNotFound("")) - return - } - - g.mustRender(w, r, newThumbnailResponse(rsp)) + g.sendThumbnailResponse(rsp, w, r) } // Thumbnail implements the Service interface. @@ -186,12 +182,7 @@ func (g Webdav) Thumbnail(w http.ResponseWriter, r *http.Request) { return } - if len(rsp.Thumbnail) == 0 { - renderError(w, r, errNotFound("")) - return - } - - g.mustRender(w, r, newThumbnailResponse(rsp)) + g.sendThumbnailResponse(rsp, w, r) } func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { @@ -231,12 +222,7 @@ func (g Webdav) PublicThumbnail(w http.ResponseWriter, r *http.Request) { return } - if len(rsp.Thumbnail) == 0 { - renderError(w, r, errNotFound("")) - return - } - - g.mustRender(w, r, newThumbnailResponse(rsp)) + g.sendThumbnailResponse(rsp, w, r) } func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { @@ -247,7 +233,7 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { return } - rsp, err := g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ + _, err = g.thumbnailsClient.GetThumbnail(r.Context(), &thumbnailssvc.GetThumbnailRequest{ Filepath: strings.TrimLeft(tr.Filepath, "/"), ThumbnailType: extensionToThumbnailType(strings.TrimLeft(tr.Extension, ".")), Width: tr.Width, @@ -276,28 +262,56 @@ func (g Webdav) PublicThumbnailHead(w http.ResponseWriter, r *http.Request) { return } - if len(rsp.Thumbnail) == 0 { - renderError(w, r, errNotFound("")) + w.WriteHeader(http.StatusOK) +} + +func (g Webdav) sendThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse, w http.ResponseWriter, r *http.Request) { + client := &http.Client{ + // Timeout: time.Second * 5, + } + + dlReq, err := http.NewRequest(http.MethodGet, rsp.DataEndpoint, http.NoBody) + if err != nil { + renderError(w, r, errInternalError(err.Error())) + g.log.Error().Err(err).Msg("could not download thumbnail") + return + } + dlReq.Header.Set("Transfer-Token", rsp.TransferToken) + + dlRsp, err := client.Do(dlReq) + if err != nil { + renderError(w, r, errInternalError(err.Error())) + g.log.Error().Err(err).Msg("could not download thumbnail") + return + } + defer dlRsp.Body.Close() + + if dlRsp.StatusCode != http.StatusOK { + g.log.Error(). + Str("transfer_token", rsp.TransferToken). + Str("data_endpoint", rsp.DataEndpoint). + Str("response_status", dlRsp.Status). + Msg("could not download thumbnail") + renderError(w, r, errInternalError("could not download thumbnail")) return } w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", rsp.Mimetype) + _, err = io.Copy(w, dlRsp.Body) + if err != nil { + g.log.Error().Err(err).Msg("failed to write thumbnail to response writer") + } } -func extensionToThumbnailType(ext string) thumbnailssvc.GetThumbnailRequest_ThumbnailType { +func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType { switch strings.ToUpper(ext) { case "GIF": - return thumbnailssvc.GetThumbnailRequest_GIF + return thumbnailsmsg.ThumbnailType_GIF case "PNG": - return thumbnailssvc.GetThumbnailRequest_PNG + return thumbnailsmsg.ThumbnailType_PNG default: - return thumbnailssvc.GetThumbnailRequest_JPG - } -} - -func (g Webdav) mustRender(w http.ResponseWriter, r *http.Request, renderer render.Renderer) { - if err := render.Render(w, r, renderer); err != nil { - g.log.Err(err).Msg("failed to write response") + return thumbnailsmsg.ThumbnailType_JPG } } @@ -337,25 +351,6 @@ func errNotFound(msg string) *errResponse { return newErrResponse(http.StatusNotFound, msg) } -type thumbnailResponse struct { - contentType string - thumbnail []byte -} - -func (t *thumbnailResponse) Render(w http.ResponseWriter, _ *http.Request) error { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", t.contentType) - _, err := w.Write(t.thumbnail) - return err -} - -func newThumbnailResponse(rsp *thumbnailssvc.GetThumbnailResponse) *thumbnailResponse { - return &thumbnailResponse{ - contentType: rsp.Mimetype, - thumbnail: rsp.Thumbnail, - } -} - func renderError(w http.ResponseWriter, r *http.Request, err *errResponse) { render.Status(r, err.HTTPStatusCode) render.XML(w, r, err)