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.
This commit is contained in:
David Christofas
2022-03-08 23:08:24 +01:00
parent d6182a4ea1
commit 95ae3b8762
30 changed files with 821 additions and 313 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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",
"format": "byte",
"title": "The thumbnail as a binary"
"description": "The endpoint where the thumbnail can be downloaded."
},
"transferToken": {
"type": "string",
"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": {

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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()
},
}

View File

@@ -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"`
}

View File

@@ -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",
},
}
}

View File

@@ -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
}

View File

@@ -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"
)

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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)
})
}

View File

@@ -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)
}

View File

@@ -0,0 +1,8 @@
package jwt
import "github.com/golang-jwt/jwt/v4"
type ThumbnailClaims struct {
jwt.RegisteredClaims
Key string `json:"key"`
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
func extensionToThumbnailType(ext string) thumbnailssvc.GetThumbnailRequest_ThumbnailType {
switch strings.ToUpper(ext) {
case "GIF":
return thumbnailssvc.GetThumbnailRequest_GIF
case "PNG":
return thumbnailssvc.GetThumbnailRequest_PNG
default:
return thumbnailssvc.GetThumbnailRequest_JPG
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 (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")
func extensionToThumbnailType(ext string) thumbnailsmsg.ThumbnailType {
switch strings.ToUpper(ext) {
case "GIF":
return thumbnailsmsg.ThumbnailType_GIF
case "PNG":
return thumbnailsmsg.ThumbnailType_PNG
default:
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)