Merge branch 'master' into no-additional-init
This commit is contained in:
@@ -1,21 +1,23 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/sync"
|
||||
|
||||
"github.com/micro/cli/v2"
|
||||
ociscfg "github.com/owncloud/ocis/ocis-pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/flagset"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/version"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/thejerf/suture/v4"
|
||||
)
|
||||
|
||||
// Execute is the entry point for the ocis-thumbnails command.
|
||||
func Execute() error {
|
||||
cfg := config.New()
|
||||
|
||||
func Execute(cfg *config.Config) error {
|
||||
app := &cli.App{
|
||||
Name: "ocis-thumbnails",
|
||||
Version: version.String,
|
||||
@@ -29,8 +31,6 @@ func Execute() error {
|
||||
},
|
||||
},
|
||||
|
||||
Flags: flagset.RootWithConfig(cfg),
|
||||
|
||||
Before: func(c *cli.Context) error {
|
||||
cfg.Server.Version = version.String
|
||||
return nil
|
||||
@@ -63,11 +63,14 @@ func NewLogger(cfg *config.Config) log.Logger {
|
||||
log.Level(cfg.Log.Level),
|
||||
log.Pretty(cfg.Log.Pretty),
|
||||
log.Color(cfg.Log.Color),
|
||||
log.File(cfg.Log.File),
|
||||
)
|
||||
}
|
||||
|
||||
// ParseConfig loads configuration from Viper known paths.
|
||||
func ParseConfig(c *cli.Context, cfg *config.Config) error {
|
||||
sync.ParsingViperConfig.Lock()
|
||||
defer sync.ParsingViperConfig.Unlock()
|
||||
logger := NewLogger(cfg)
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
@@ -108,3 +111,28 @@ func ParseConfig(c *cli.Context, cfg *config.Config) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SutureService allows for the thumbnails command to be embedded and supervised by a suture supervisor tree.
|
||||
type SutureService struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewSutureService creates a new thumbnails.SutureService
|
||||
func NewSutureService(cfg *ociscfg.Config) suture.Service {
|
||||
if cfg.Mode == 0 {
|
||||
cfg.Thumbnails.Supervised = true
|
||||
}
|
||||
cfg.Thumbnails.Log.File = cfg.Log.File
|
||||
return SutureService{
|
||||
cfg: cfg.Thumbnails,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SutureService) Serve(ctx context.Context) error {
|
||||
s.cfg.Context = ctx
|
||||
if err := Execute(s.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,24 +3,16 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"contrib.go.opencensus.io/exporter/jaeger"
|
||||
"contrib.go.opencensus.io/exporter/ocagent"
|
||||
"contrib.go.opencensus.io/exporter/zipkin"
|
||||
"github.com/micro/cli/v2"
|
||||
"github.com/oklog/run"
|
||||
openzipkin "github.com/openzipkin/zipkin-go"
|
||||
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"github.com/owncloud/ocis/ocis-pkg/sync"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/flagset"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/metrics"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/server/debug"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/server/grpc"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/tracing"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
@@ -29,103 +21,31 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
Name: "server",
|
||||
Usage: "Start integrated server",
|
||||
Flags: flagset.ServerWithConfig(cfg),
|
||||
Before: func(c *cli.Context) error {
|
||||
cfg.Thumbnail.Resolutions = c.StringSlice("thumbnail-resolution")
|
||||
Before: func(ctx *cli.Context) error {
|
||||
logger := NewLogger(cfg)
|
||||
cfg.Thumbnail.Resolutions = ctx.StringSlice("thumbnail-resolution")
|
||||
|
||||
return ParseConfig(c, cfg)
|
||||
if !cfg.Supervised {
|
||||
return ParseConfig(ctx, cfg)
|
||||
}
|
||||
logger.Debug().Str("service", "thumbnails").Msg("ignoring config file parsing when running supervised")
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
logger := NewLogger(cfg)
|
||||
if cfg.Tracing.Enabled {
|
||||
switch t := cfg.Tracing.Type; t {
|
||||
case "agent":
|
||||
exporter, err := ocagent.NewExporter(
|
||||
ocagent.WithReconnectionPeriod(5*time.Second),
|
||||
ocagent.WithAddress(cfg.Tracing.Endpoint),
|
||||
ocagent.WithServiceName(cfg.Tracing.Service),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create agent tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
view.RegisterExporter(exporter)
|
||||
|
||||
case "jaeger":
|
||||
exporter, err := jaeger.NewExporter(
|
||||
jaeger.Options{
|
||||
AgentEndpoint: cfg.Tracing.Endpoint,
|
||||
CollectorEndpoint: cfg.Tracing.Collector,
|
||||
Process: jaeger.Process{
|
||||
ServiceName: cfg.Tracing.Service,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create jaeger tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
|
||||
case "zipkin":
|
||||
endpoint, err := openzipkin.NewEndpoint(
|
||||
cfg.Tracing.Service,
|
||||
cfg.Tracing.Endpoint,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create zipkin tracing")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
exporter := zipkin.NewExporter(
|
||||
zipkinhttp.NewReporter(
|
||||
cfg.Tracing.Collector,
|
||||
),
|
||||
endpoint,
|
||||
)
|
||||
|
||||
trace.RegisterExporter(exporter)
|
||||
|
||||
default:
|
||||
logger.Warn().
|
||||
Str("type", t).
|
||||
Msg("Unknown tracing backend")
|
||||
}
|
||||
|
||||
trace.ApplyConfig(
|
||||
trace.Config{
|
||||
DefaultSampler: trace.AlwaysSample(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
logger.Debug().
|
||||
Msg("Tracing is not enabled")
|
||||
if err := tracing.Configure(cfg, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
gr = run.Group{}
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
metrics = metrics.New()
|
||||
ctx, cancel = func() (context.Context, context.CancelFunc) {
|
||||
if cfg.Context == nil {
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
return context.WithCancel(cfg.Context)
|
||||
}()
|
||||
metrics = metrics.New()
|
||||
)
|
||||
|
||||
defer cancel()
|
||||
@@ -140,7 +60,6 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
grpc.Namespace(cfg.Server.Namespace),
|
||||
grpc.Address(cfg.Server.Address),
|
||||
grpc.Metrics(metrics),
|
||||
grpc.Flags(flagset.RootWithConfig(config.New())),
|
||||
)
|
||||
|
||||
gr.Add(func() error {
|
||||
@@ -156,47 +75,17 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Info().
|
||||
Err(err).
|
||||
Str("transport", "debug").
|
||||
Msg("Failed to initialize server")
|
||||
|
||||
logger.Info().Err(err).Str("transport", "debug").Msg("Failed to initialize server")
|
||||
return err
|
||||
}
|
||||
|
||||
gr.Add(func() error {
|
||||
return server.ListenAndServe()
|
||||
}, func(_ error) {
|
||||
ctx, timeout := context.WithTimeout(ctx, 5*time.Second)
|
||||
|
||||
defer timeout()
|
||||
defer cancel()
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
logger.Info().
|
||||
Err(err).
|
||||
Str("transport", "debug").
|
||||
Msg("Failed to shutdown server")
|
||||
} else {
|
||||
logger.Info().
|
||||
Str("transport", "debug").
|
||||
Msg("Shutting down server")
|
||||
}
|
||||
gr.Add(server.ListenAndServe, func(_ error) {
|
||||
_ = server.Shutdown(ctx)
|
||||
cancel()
|
||||
})
|
||||
|
||||
{
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
||||
gr.Add(func() error {
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
|
||||
<-stop
|
||||
|
||||
return nil
|
||||
}, func(err error) {
|
||||
close(stop)
|
||||
cancel()
|
||||
})
|
||||
if !cfg.Supervised {
|
||||
sync.Trap(&gr, cancel)
|
||||
}
|
||||
|
||||
return gr.Run()
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package config
|
||||
|
||||
import "context"
|
||||
|
||||
// Log defines the available logging configuration.
|
||||
type Log struct {
|
||||
Level string
|
||||
Pretty bool
|
||||
Color bool
|
||||
File string
|
||||
}
|
||||
|
||||
// Debug defines the available debug configuration.
|
||||
@@ -40,6 +43,9 @@ type Config struct {
|
||||
Server Server
|
||||
Tracing Tracing
|
||||
Thumbnail Thumbnail
|
||||
|
||||
Context context.Context
|
||||
Supervised bool
|
||||
}
|
||||
|
||||
// FileSystemStorage defines the available filesystem storage configuration.
|
||||
@@ -47,12 +53,6 @@ type FileSystemStorage struct {
|
||||
RootDirectory string
|
||||
}
|
||||
|
||||
// WebDavSource defines the available webdav source configuration.
|
||||
type WebDavSource struct {
|
||||
BaseURL string
|
||||
Insecure bool
|
||||
}
|
||||
|
||||
// FileSystemSource defines the available filesystem source configuration.
|
||||
type FileSystemSource struct {
|
||||
BasePath string
|
||||
@@ -60,9 +60,11 @@ type FileSystemSource struct {
|
||||
|
||||
// Thumbnail defines the available thumbnail related configuration.
|
||||
type Thumbnail struct {
|
||||
Resolutions []string
|
||||
FileSystemStorage FileSystemStorage
|
||||
WebDavSource WebDavSource
|
||||
Resolutions []string
|
||||
FileSystemStorage FileSystemStorage
|
||||
WebdavAllowInsecure bool
|
||||
RevaGateway string
|
||||
WebdavNamespace string
|
||||
}
|
||||
|
||||
// New initializes a new configuration with or without defaults.
|
||||
|
||||
@@ -1,46 +1,18 @@
|
||||
package flagset
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"github.com/owncloud/ocis/ocis-pkg/flags"
|
||||
|
||||
"github.com/micro/cli/v2"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
)
|
||||
|
||||
// RootWithConfig applies cfg to the root flagset
|
||||
func RootWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Value: "info",
|
||||
Usage: "Set logging level",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_LEVEL"},
|
||||
Destination: &cfg.Log.Level,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-pretty",
|
||||
Value: true,
|
||||
Usage: "Enable pretty logging",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_PRETTY"},
|
||||
Destination: &cfg.Log.Pretty,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-color",
|
||||
Value: true,
|
||||
Usage: "Enable colored logging",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_COLOR"},
|
||||
Destination: &cfg.Log.Color,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HealthWithConfig applies cfg to the root flagset
|
||||
func HealthWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "debug-addr",
|
||||
Value: "0.0.0.0:9189",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Addr, "0.0.0.0:9189"),
|
||||
Usage: "Address to debug endpoint",
|
||||
EnvVars: []string{"THUMBNAILS_DEBUG_ADDR"},
|
||||
Destination: &cfg.Debug.Addr,
|
||||
@@ -51,6 +23,30 @@ func HealthWithConfig(cfg *config.Config) []cli.Flag {
|
||||
// ServerWithConfig applies cfg to the root flagset
|
||||
func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-file",
|
||||
Usage: "Enable log to file",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_FILE", "OCIS_LOG_FILE"},
|
||||
Destination: &cfg.Log.File,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Usage: "Set logging level",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_LEVEL", "OCIS_LOG_LEVEL"},
|
||||
Destination: &cfg.Log.Level,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-pretty",
|
||||
Usage: "Enable pretty logging",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_PRETTY", "OCIS_LOG_PRETTY"},
|
||||
Destination: &cfg.Log.Pretty,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "log-color",
|
||||
Usage: "Enable colored logging",
|
||||
EnvVars: []string{"THUMBNAILS_LOG_COLOR", "OCIS_LOG_COLOR"},
|
||||
Destination: &cfg.Log.Color,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config-file",
|
||||
Value: "",
|
||||
@@ -66,42 +62,42 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-type",
|
||||
Value: "jaeger",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Type, "jaeger"),
|
||||
Usage: "Tracing backend type",
|
||||
EnvVars: []string{"THUMBNAILS_TRACING_TYPE"},
|
||||
Destination: &cfg.Tracing.Type,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-endpoint",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Endpoint, ""),
|
||||
Usage: "Endpoint for the agent",
|
||||
EnvVars: []string{"THUMBNAILS_TRACING_ENDPOINT"},
|
||||
Destination: &cfg.Tracing.Endpoint,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-collector",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Collector, ""),
|
||||
Usage: "Endpoint for the collector",
|
||||
EnvVars: []string{"THUMBNAILS_TRACING_COLLECTOR"},
|
||||
Destination: &cfg.Tracing.Collector,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "tracing-service",
|
||||
Value: "thumbnails",
|
||||
Value: flags.OverrideDefaultString(cfg.Tracing.Service, "thumbnails"),
|
||||
Usage: "Service name for tracing",
|
||||
EnvVars: []string{"THUMBNAILS_TRACING_SERVICE"},
|
||||
Destination: &cfg.Tracing.Service,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "debug-addr",
|
||||
Value: "0.0.0.0:9189",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Addr, "0.0.0.0:9189"),
|
||||
Usage: "Address to bind debug server",
|
||||
EnvVars: []string{"THUMBNAILS_DEBUG_ADDR"},
|
||||
Destination: &cfg.Debug.Addr,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "debug-token",
|
||||
Value: "",
|
||||
Value: flags.OverrideDefaultString(cfg.Debug.Token, ""),
|
||||
Usage: "Token to grant metrics access",
|
||||
EnvVars: []string{"THUMBNAILS_DEBUG_TOKEN"},
|
||||
Destination: &cfg.Debug.Token,
|
||||
@@ -120,45 +116,45 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "grpc-name",
|
||||
Value: "thumbnails",
|
||||
Value: flags.OverrideDefaultString(cfg.Server.Name, "thumbnails"),
|
||||
Usage: "Name of the service",
|
||||
EnvVars: []string{"THUMBNAILS_GRPC_NAME"},
|
||||
Destination: &cfg.Server.Name,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "grpc-addr",
|
||||
Value: "0.0.0.0:9185",
|
||||
Value: flags.OverrideDefaultString(cfg.Server.Address, "0.0.0.0:9185"),
|
||||
Usage: "Address to bind grpc server",
|
||||
EnvVars: []string{"THUMBNAILS_GRPC_ADDR"},
|
||||
Destination: &cfg.Server.Address,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "grpc-namespace",
|
||||
Value: "com.owncloud.api",
|
||||
Value: flags.OverrideDefaultString(cfg.Server.Namespace, "com.owncloud.api"),
|
||||
Usage: "Set the base namespace for the grpc namespace",
|
||||
EnvVars: []string{"THUMBNAILS_GRPC_NAMESPACE"},
|
||||
Destination: &cfg.Server.Namespace,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "filesystemstorage-root",
|
||||
Value: filepath.Join(os.TempDir(), "ocis-thumbnails/"),
|
||||
Value: "/var/tmp/ocis/thumbnails",
|
||||
Usage: "Root path of the filesystem storage directory",
|
||||
EnvVars: []string{"THUMBNAILS_FILESYSTEMSTORAGE_ROOT"},
|
||||
Destination: &cfg.Thumbnail.FileSystemStorage.RootDirectory,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "webdavsource-baseurl",
|
||||
Value: "https://localhost:9200/remote.php/webdav/",
|
||||
Usage: "Base url for a webdav api",
|
||||
EnvVars: []string{"THUMBNAILS_WEBDAVSOURCE_BASEURL"},
|
||||
Destination: &cfg.Thumbnail.WebDavSource.BaseURL,
|
||||
Name: "reva-gateway-addr",
|
||||
Value: flags.OverrideDefaultString(cfg.Thumbnail.RevaGateway, "127.0.0.1:9142"),
|
||||
Usage: "Reva gateway address",
|
||||
EnvVars: []string{"THUMBNAILS_REVA_GATEWAY", "PROXY_REVA_GATEWAY_ADDR"},
|
||||
Destination: &cfg.Thumbnail.RevaGateway,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "webdavsource-insecure",
|
||||
Value: true,
|
||||
Value: flags.OverrideDefaultBool(cfg.Thumbnail.WebdavAllowInsecure, true),
|
||||
Usage: "Whether to skip certificate checks",
|
||||
EnvVars: []string{"THUMBNAILS_WEBDAVSOURCE_INSECURE"},
|
||||
Destination: &cfg.Thumbnail.WebDavSource.Insecure,
|
||||
Destination: &cfg.Thumbnail.WebdavAllowInsecure,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "thumbnail-resolution",
|
||||
@@ -166,6 +162,17 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
Usage: "--thumbnail-resolution 16x16 [--thumbnail-resolution 32x32]",
|
||||
EnvVars: []string{"THUMBNAILS_RESOLUTIONS"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "webdav-namespace",
|
||||
Value: flags.OverrideDefaultString(cfg.Thumbnail.WebdavNamespace, "/home"),
|
||||
Usage: "Namespace prefix for the webdav endpoint",
|
||||
EnvVars: []string{"STORAGE_WEBDAV_NAMESPACE"},
|
||||
Destination: &cfg.Thumbnail.WebdavNamespace,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "extensions",
|
||||
Usage: "Run specific extensions during supervised mode",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,14 +181,14 @@ func ListThumbnailsWithConfig(cfg *config.Config) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "grpc-name",
|
||||
Value: "thumbnails",
|
||||
Value: flags.OverrideDefaultString(cfg.Server.Name, "thumbnails"),
|
||||
Usage: "Name of the service",
|
||||
EnvVars: []string{"THUMBNAILS_GRPC_NAME"},
|
||||
Destination: &cfg.Server.Name,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "grpc-namespace",
|
||||
Value: "com.owncloud.api",
|
||||
Value: flags.OverrideDefaultString(cfg.Server.Namespace, "com.owncloud.api"),
|
||||
Usage: "Set the base namespace for the grpc namespace",
|
||||
EnvVars: []string{"THUMBNAILS_GRPC_NAMESPACE"},
|
||||
Destination: &cfg.Server.Namespace,
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package preprocessor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"github.com/golang/freetype"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/gofont/goregular"
|
||||
"image"
|
||||
"image/draw"
|
||||
"io"
|
||||
"mime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
fontSize = 12
|
||||
spacing float64 = 1.5
|
||||
)
|
||||
|
||||
type FileConverter interface {
|
||||
Convert(r io.Reader) (image.Image, error)
|
||||
}
|
||||
|
||||
type ImageDecoder struct{}
|
||||
|
||||
func (i ImageDecoder) Convert(r io.Reader) (image.Image, error) {
|
||||
img, _, err := image.Decode(r)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, `could not decode the image`)
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
type TxtToImageConverter struct{}
|
||||
|
||||
func (t TxtToImageConverter) Convert(r io.Reader) (image.Image, error) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
||||
draw.Draw(img, img.Bounds(), image.White, image.Point{}, draw.Src)
|
||||
|
||||
c := freetype.NewContext()
|
||||
// Ignoring the error since we are using the embedded Golang font.
|
||||
// This shouldn't return an error.
|
||||
f, _ := truetype.Parse(goregular.TTF)
|
||||
c.SetFont(f)
|
||||
c.SetFontSize(fontSize)
|
||||
c.SetClip(img.Bounds())
|
||||
c.SetDst(img)
|
||||
c.SetSrc(image.Black)
|
||||
c.SetHinting(font.HintingFull)
|
||||
pt := freetype.Pt(10, 10+int(c.PointToFixed(fontSize)>>6))
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
txt := scanner.Text()
|
||||
cs := chunks(txt, 80)
|
||||
for _, s := range cs {
|
||||
_, err := c.DrawString(strings.TrimSpace(s), pt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pt.Y += c.PointToFixed(fontSize * spacing)
|
||||
if pt.Y.Round() >= img.Bounds().Dy() {
|
||||
return img, scanner.Err()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return img, scanner.Err()
|
||||
}
|
||||
|
||||
// Code from https://stackoverflow.com/a/61469854
|
||||
// Written By Igor Mikushkin
|
||||
func chunks(s string, chunkSize int) []string {
|
||||
if chunkSize >= len(s) {
|
||||
return []string{s}
|
||||
}
|
||||
var chunks []string
|
||||
chunk := make([]rune, chunkSize)
|
||||
length := 0
|
||||
for _, r := range s {
|
||||
chunk[length] = r
|
||||
length++
|
||||
if length == chunkSize {
|
||||
chunks = append(chunks, string(chunk))
|
||||
length = 0
|
||||
}
|
||||
}
|
||||
if length > 0 {
|
||||
chunks = append(chunks, string(chunk[:length]))
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
|
||||
func ForType(mimeType string) FileConverter {
|
||||
// We can ignore the error here because we parse it in IsMimeTypeSupported before and if it fails
|
||||
// return the service call. So we should only get here when the mimeType parses fine.
|
||||
mimeType, _, _ = mime.ParseMediaType(mimeType)
|
||||
switch mimeType {
|
||||
case "text/plain":
|
||||
return TxtToImageConverter{}
|
||||
default:
|
||||
return ImageDecoder{}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.14.0
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.2
|
||||
// source: thumbnails.proto
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -22,59 +21,55 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
// The file types to which the thumbnail cna get encoded to.
|
||||
type GetRequest_FileType int32
|
||||
// The file types to which the thumbnail can get encoded to.
|
||||
type GetThumbnailRequest_ThumbnailType int32
|
||||
|
||||
const (
|
||||
GetRequest_PNG GetRequest_FileType = 0 // Represents PNG type
|
||||
GetRequest_JPG GetRequest_FileType = 1 // Represents JPG type
|
||||
GetThumbnailRequest_PNG GetThumbnailRequest_ThumbnailType = 0 // Represents PNG type
|
||||
GetThumbnailRequest_JPG GetThumbnailRequest_ThumbnailType = 1 // Represents JPG type
|
||||
)
|
||||
|
||||
// Enum value maps for GetRequest_FileType.
|
||||
// Enum value maps for GetThumbnailRequest_ThumbnailType.
|
||||
var (
|
||||
GetRequest_FileType_name = map[int32]string{
|
||||
GetThumbnailRequest_ThumbnailType_name = map[int32]string{
|
||||
0: "PNG",
|
||||
1: "JPG",
|
||||
}
|
||||
GetRequest_FileType_value = map[string]int32{
|
||||
GetThumbnailRequest_ThumbnailType_value = map[string]int32{
|
||||
"PNG": 0,
|
||||
"JPG": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x GetRequest_FileType) Enum() *GetRequest_FileType {
|
||||
p := new(GetRequest_FileType)
|
||||
func (x GetThumbnailRequest_ThumbnailType) Enum() *GetThumbnailRequest_ThumbnailType {
|
||||
p := new(GetThumbnailRequest_ThumbnailType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x GetRequest_FileType) String() string {
|
||||
func (x GetThumbnailRequest_ThumbnailType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (GetRequest_FileType) Descriptor() protoreflect.EnumDescriptor {
|
||||
func (GetThumbnailRequest_ThumbnailType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_thumbnails_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (GetRequest_FileType) Type() protoreflect.EnumType {
|
||||
func (GetThumbnailRequest_ThumbnailType) Type() protoreflect.EnumType {
|
||||
return &file_thumbnails_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x GetRequest_FileType) Number() protoreflect.EnumNumber {
|
||||
func (x GetThumbnailRequest_ThumbnailType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetRequest_FileType.Descriptor instead.
|
||||
func (GetRequest_FileType) EnumDescriptor() ([]byte, []int) {
|
||||
// Deprecated: Use GetThumbnailRequest_ThumbnailType.Descriptor instead.
|
||||
func (GetThumbnailRequest_ThumbnailType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_thumbnails_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
// A request to retrieve a thumbnail
|
||||
type GetRequest struct {
|
||||
type GetThumbnailRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
@@ -82,21 +77,19 @@ type GetRequest 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.
|
||||
Filetype GetRequest_FileType `protobuf:"varint,2,opt,name=filetype,proto3,enum=com.owncloud.ocis.thumbnails.v0.GetRequest_FileType" json:"filetype,omitempty"`
|
||||
// The etag of the source image
|
||||
Etag string `protobuf:"bytes,3,opt,name=etag,proto3" json:"etag,omitempty"`
|
||||
ThumbnailType GetThumbnailRequest_ThumbnailType `protobuf:"varint,2,opt,name=thumbnail_type,json=thumbnailType,proto3,enum=com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest_ThumbnailType" json:"thumbnail_type,omitempty"`
|
||||
// The width of the thumbnail
|
||||
Width int32 `protobuf:"varint,4,opt,name=width,proto3" json:"width,omitempty"`
|
||||
Width int32 `protobuf:"varint,3,opt,name=width,proto3" json:"width,omitempty"`
|
||||
// The height of the thumbnail
|
||||
Height int32 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"`
|
||||
// The authorization token
|
||||
Authorization string `protobuf:"bytes,6,opt,name=authorization,proto3" json:"authorization,omitempty"`
|
||||
// The user requesting the resource.
|
||||
Username string `protobuf:"bytes,7,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Height int32 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"`
|
||||
// Types that are assignable to Source:
|
||||
// *GetThumbnailRequest_WebdavSource
|
||||
// *GetThumbnailRequest_Cs3Source
|
||||
Source isGetThumbnailRequest_Source `protobuf_oneof:"source"`
|
||||
}
|
||||
|
||||
func (x *GetRequest) Reset() {
|
||||
*x = GetRequest{}
|
||||
func (x *GetThumbnailRequest) Reset() {
|
||||
*x = GetThumbnailRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_thumbnails_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -104,13 +97,13 @@ func (x *GetRequest) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetRequest) String() string {
|
||||
func (x *GetThumbnailRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetRequest) ProtoMessage() {}
|
||||
func (*GetThumbnailRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetRequest) ProtoReflect() protoreflect.Message {
|
||||
func (x *GetThumbnailRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_thumbnails_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -122,74 +115,95 @@ func (x *GetRequest) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetRequest) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use GetThumbnailRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetThumbnailRequest) Descriptor() ([]byte, []int) {
|
||||
return file_thumbnails_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetFilepath() string {
|
||||
func (x *GetThumbnailRequest) GetFilepath() string {
|
||||
if x != nil {
|
||||
return x.Filepath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetFiletype() GetRequest_FileType {
|
||||
func (x *GetThumbnailRequest) GetThumbnailType() GetThumbnailRequest_ThumbnailType {
|
||||
if x != nil {
|
||||
return x.Filetype
|
||||
return x.ThumbnailType
|
||||
}
|
||||
return GetRequest_PNG
|
||||
return GetThumbnailRequest_PNG
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetEtag() string {
|
||||
if x != nil {
|
||||
return x.Etag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetWidth() int32 {
|
||||
func (x *GetThumbnailRequest) GetWidth() int32 {
|
||||
if x != nil {
|
||||
return x.Width
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetHeight() int32 {
|
||||
func (x *GetThumbnailRequest) GetHeight() int32 {
|
||||
if x != nil {
|
||||
return x.Height
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetAuthorization() string {
|
||||
if x != nil {
|
||||
return x.Authorization
|
||||
func (m *GetThumbnailRequest) GetSource() isGetThumbnailRequest_Source {
|
||||
if m != nil {
|
||||
return m.Source
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GetRequest) GetUsername() string {
|
||||
if x != nil {
|
||||
return x.Username
|
||||
func (x *GetThumbnailRequest) GetWebdavSource() *WebdavSource {
|
||||
if x, ok := x.GetSource().(*GetThumbnailRequest_WebdavSource); ok {
|
||||
return x.WebdavSource
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// The service response
|
||||
type GetResponse struct {
|
||||
func (x *GetThumbnailRequest) GetCs3Source() *CS3Source {
|
||||
if x, ok := x.GetSource().(*GetThumbnailRequest_Cs3Source); ok {
|
||||
return x.Cs3Source
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isGetThumbnailRequest_Source interface {
|
||||
isGetThumbnailRequest_Source()
|
||||
}
|
||||
|
||||
type GetThumbnailRequest_WebdavSource struct {
|
||||
WebdavSource *WebdavSource `protobuf:"bytes,5,opt,name=webdav_source,json=webdavSource,proto3,oneof"`
|
||||
}
|
||||
|
||||
type GetThumbnailRequest_Cs3Source struct {
|
||||
Cs3Source *CS3Source `protobuf:"bytes,6,opt,name=cs3_source,json=cs3Source,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*GetThumbnailRequest_WebdavSource) isGetThumbnailRequest_Source() {}
|
||||
|
||||
func (*GetThumbnailRequest_Cs3Source) isGetThumbnailRequest_Source() {}
|
||||
|
||||
type WebdavSource struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The thumbnail as a binary
|
||||
Thumbnail []byte `protobuf:"bytes,1,opt,name=thumbnail,proto3" json:"thumbnail,omitempty"`
|
||||
// The mimetype of the thumbnail
|
||||
Mimetype string `protobuf:"bytes,2,opt,name=mimetype,proto3" json:"mimetype,omitempty"`
|
||||
// REQUIRED.
|
||||
Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
|
||||
// REQUIRED.
|
||||
IsPublicLink bool `protobuf:"varint,2,opt,name=is_public_link,json=isPublicLink,proto3" json:"is_public_link,omitempty"`
|
||||
// OPTIONAL.
|
||||
WebdavAuthorization string `protobuf:"bytes,3,opt,name=webdav_authorization,json=webdavAuthorization,proto3" json:"webdav_authorization,omitempty"`
|
||||
// OPTIONAL.
|
||||
RevaAuthorization string `protobuf:"bytes,4,opt,name=reva_authorization,json=revaAuthorization,proto3" json:"reva_authorization,omitempty"`
|
||||
// OPTIONAL.
|
||||
PublicLinkToken string `protobuf:"bytes,5,opt,name=public_link_token,json=publicLinkToken,proto3" json:"public_link_token,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetResponse) Reset() {
|
||||
*x = GetResponse{}
|
||||
func (x *WebdavSource) Reset() {
|
||||
*x = WebdavSource{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_thumbnails_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -197,13 +211,13 @@ func (x *GetResponse) Reset() {
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetResponse) String() string {
|
||||
func (x *WebdavSource) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetResponse) ProtoMessage() {}
|
||||
func (*WebdavSource) ProtoMessage() {}
|
||||
|
||||
func (x *GetResponse) ProtoReflect() protoreflect.Message {
|
||||
func (x *WebdavSource) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_thumbnails_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
@@ -215,19 +229,153 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message {
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetResponse) Descriptor() ([]byte, []int) {
|
||||
// Deprecated: Use WebdavSource.ProtoReflect.Descriptor instead.
|
||||
func (*WebdavSource) Descriptor() ([]byte, []int) {
|
||||
return file_thumbnails_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *GetResponse) GetThumbnail() []byte {
|
||||
func (x *WebdavSource) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WebdavSource) GetIsPublicLink() bool {
|
||||
if x != nil {
|
||||
return x.IsPublicLink
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *WebdavSource) GetWebdavAuthorization() string {
|
||||
if x != nil {
|
||||
return x.WebdavAuthorization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WebdavSource) GetRevaAuthorization() string {
|
||||
if x != nil {
|
||||
return x.RevaAuthorization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *WebdavSource) GetPublicLinkToken() string {
|
||||
if x != nil {
|
||||
return x.PublicLinkToken
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type CS3Source struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
|
||||
Authorization string `protobuf:"bytes,2,opt,name=authorization,proto3" json:"authorization,omitempty"`
|
||||
}
|
||||
|
||||
func (x *CS3Source) Reset() {
|
||||
*x = CS3Source{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_thumbnails_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *CS3Source) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*CS3Source) ProtoMessage() {}
|
||||
|
||||
func (x *CS3Source) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_thumbnails_proto_msgTypes[2]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use CS3Source.ProtoReflect.Descriptor instead.
|
||||
func (*CS3Source) Descriptor() ([]byte, []int) {
|
||||
return file_thumbnails_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *CS3Source) GetPath() string {
|
||||
if x != nil {
|
||||
return x.Path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *CS3Source) GetAuthorization() string {
|
||||
if x != nil {
|
||||
return x.Authorization
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// The service response
|
||||
type GetThumbnailResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The thumbnail as a binary
|
||||
Thumbnail []byte `protobuf:"bytes,1,opt,name=thumbnail,proto3" json:"thumbnail,omitempty"`
|
||||
// The mimetype of the thumbnail
|
||||
Mimetype string `protobuf:"bytes,2,opt,name=mimetype,proto3" json:"mimetype,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetThumbnailResponse) Reset() {
|
||||
*x = GetThumbnailResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_thumbnails_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetThumbnailResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetThumbnailResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetThumbnailResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_thumbnails_proto_msgTypes[3]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetThumbnailResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetThumbnailResponse) Descriptor() ([]byte, []int) {
|
||||
return file_thumbnails_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *GetThumbnailResponse) GetThumbnail() []byte {
|
||||
if x != nil {
|
||||
return x.Thumbnail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GetResponse) GetMimetype() string {
|
||||
func (x *GetThumbnailResponse) GetMimetype() string {
|
||||
if x != nil {
|
||||
return x.Mimetype
|
||||
}
|
||||
@@ -243,58 +391,87 @@ var file_thumbnails_proto_rawDesc = []byte{
|
||||
0x2e, 0x76, 0x30, 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, 0x9c, 0x02, 0x0a, 0x0a, 0x47, 0x65, 0x74, 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, 0x50,
|
||||
0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e,
|
||||
0x6f, 0x63, 0x69, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e,
|
||||
0x76, 0x30, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69,
|
||||
0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x74, 0x79, 0x70, 0x65,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x65, 0x74, 0x61, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x65, 0x74, 0x61, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x04, 0x20,
|
||||
0x01, 0x28, 0x05, 0x52, 0x05, 0x77, 0x69, 0x64, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65,
|
||||
0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67,
|
||||
0x68, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f,
|
||||
0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1c, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65,
|
||||
0x12, 0x07, 0x0a, 0x03, 0x50, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x4a, 0x50, 0x47,
|
||||
0x10, 0x01, 0x22, 0x47, 0x0a, 0x0b, 0x47, 0x65, 0x74, 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, 0x7d, 0x0a, 0x10, 0x54,
|
||||
0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
|
||||
0x69, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12,
|
||||
0x2b, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x9a, 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, 0x69, 0x0a, 0x0e, 0x74, 0x68, 0x75, 0x6d, 0x62,
|
||||
0x6e, 0x61, 0x69, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
|
||||
0x42, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f,
|
||||
0x63, 0x69, 0x73, 0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76,
|
||||
0x30, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63,
|
||||
0x6f, 0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x63, 0x69, 0x73,
|
||||
0x2e, 0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x76, 0x30, 0x2e, 0x47,
|
||||
0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xc2, 0x02, 0x5a, 0x12, 0x70,
|
||||
0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x3b, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x92, 0x41, 0xaa, 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, 0x45, 0x0a, 0x10, 0x44, 0x65, 0x76, 0x65,
|
||||
0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x12, 0x31, 0x68, 0x74,
|
||||
0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x69, 0x6f, 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,
|
||||
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, 0x54, 0x0a, 0x0d, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x77,
|
||||
0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x63, 0x69, 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, 0x4b, 0x0a, 0x0a, 0x63, 0x73, 0x33, 0x5f, 0x73, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x63, 0x69, 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, 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, 0xd4, 0x01, 0x0a, 0x0c, 0x57, 0x65, 0x62, 0x64, 0x61, 0x76, 0x53, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||
0x75, 0x72, 0x6c, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
||||
0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x50,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x31, 0x0a, 0x14, 0x77, 0x65, 0x62,
|
||||
0x64, 0x61, 0x76, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x65, 0x62, 0x64, 0x61, 0x76, 0x41,
|
||||
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12,
|
||||
0x72, 0x65, 0x76, 0x61, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x72, 0x65, 0x76, 0x61, 0x41, 0x75,
|
||||
0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x70,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4c, 0x69,
|
||||
0x6e, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x45, 0x0a, 0x09, 0x43, 0x53, 0x33, 0x53, 0x6f,
|
||||
0x75, 0x72, 0x63, 0x65, 0x12, 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, 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, 0x8f, 0x01, 0x0a, 0x10, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x53, 0x65,
|
||||
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7b, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x68, 0x75, 0x6d,
|
||||
0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x34, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63,
|
||||
0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x63, 0x69, 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, 0x35, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2e, 0x6f, 0x77, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x6f, 0x63, 0x69, 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, 0xe0, 0x02, 0x5a, 0x36, 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,
|
||||
0x74, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x73, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x30, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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 (
|
||||
@@ -310,21 +487,25 @@ func file_thumbnails_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_thumbnails_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_thumbnails_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_thumbnails_proto_goTypes = []interface{}{
|
||||
(GetRequest_FileType)(0), // 0: com.owncloud.ocis.thumbnails.v0.GetRequest.FileType
|
||||
(*GetRequest)(nil), // 1: com.owncloud.ocis.thumbnails.v0.GetRequest
|
||||
(*GetResponse)(nil), // 2: com.owncloud.ocis.thumbnails.v0.GetResponse
|
||||
(GetThumbnailRequest_ThumbnailType)(0), // 0: com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest.ThumbnailType
|
||||
(*GetThumbnailRequest)(nil), // 1: com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest
|
||||
(*WebdavSource)(nil), // 2: com.owncloud.ocis.thumbnails.v0.WebdavSource
|
||||
(*CS3Source)(nil), // 3: com.owncloud.ocis.thumbnails.v0.CS3Source
|
||||
(*GetThumbnailResponse)(nil), // 4: com.owncloud.ocis.thumbnails.v0.GetThumbnailResponse
|
||||
}
|
||||
var file_thumbnails_proto_depIdxs = []int32{
|
||||
0, // 0: com.owncloud.ocis.thumbnails.v0.GetRequest.filetype:type_name -> com.owncloud.ocis.thumbnails.v0.GetRequest.FileType
|
||||
1, // 1: com.owncloud.ocis.thumbnails.v0.ThumbnailService.GetThumbnail:input_type -> com.owncloud.ocis.thumbnails.v0.GetRequest
|
||||
2, // 2: com.owncloud.ocis.thumbnails.v0.ThumbnailService.GetThumbnail:output_type -> com.owncloud.ocis.thumbnails.v0.GetResponse
|
||||
2, // [2:3] is the sub-list for method output_type
|
||||
1, // [1:2] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
0, // 0: com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest.thumbnail_type:type_name -> com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest.ThumbnailType
|
||||
2, // 1: com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest.webdav_source:type_name -> com.owncloud.ocis.thumbnails.v0.WebdavSource
|
||||
3, // 2: com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest.cs3_source:type_name -> com.owncloud.ocis.thumbnails.v0.CS3Source
|
||||
1, // 3: com.owncloud.ocis.thumbnails.v0.ThumbnailService.GetThumbnail:input_type -> com.owncloud.ocis.thumbnails.v0.GetThumbnailRequest
|
||||
4, // 4: com.owncloud.ocis.thumbnails.v0.ThumbnailService.GetThumbnail:output_type -> com.owncloud.ocis.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
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_thumbnails_proto_init() }
|
||||
@@ -334,7 +515,7 @@ func file_thumbnails_proto_init() {
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_thumbnails_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetRequest); i {
|
||||
switch v := v.(*GetThumbnailRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -346,7 +527,31 @@ func file_thumbnails_proto_init() {
|
||||
}
|
||||
}
|
||||
file_thumbnails_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetResponse); i {
|
||||
switch v := v.(*WebdavSource); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_thumbnails_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*CS3Source); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_thumbnails_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetThumbnailResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -358,13 +563,17 @@ func file_thumbnails_proto_init() {
|
||||
}
|
||||
}
|
||||
}
|
||||
file_thumbnails_proto_msgTypes[0].OneofWrappers = []interface{}{
|
||||
(*GetThumbnailRequest_WebdavSource)(nil),
|
||||
(*GetThumbnailRequest_Cs3Source)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_thumbnails_proto_rawDesc,
|
||||
NumEnums: 1,
|
||||
NumMessages: 2,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@ func NewThumbnailServiceEndpoints() []*api.Endpoint {
|
||||
|
||||
type ThumbnailService interface {
|
||||
// Generates the thumbnail and returns it.
|
||||
GetThumbnail(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetResponse, error)
|
||||
GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...client.CallOption) (*GetThumbnailResponse, error)
|
||||
}
|
||||
|
||||
type thumbnailService struct {
|
||||
@@ -59,9 +59,9 @@ func NewThumbnailService(name string, c client.Client) ThumbnailService {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *thumbnailService) GetThumbnail(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetResponse, error) {
|
||||
func (c *thumbnailService) GetThumbnail(ctx context.Context, in *GetThumbnailRequest, opts ...client.CallOption) (*GetThumbnailResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "ThumbnailService.GetThumbnail", in)
|
||||
out := new(GetResponse)
|
||||
out := new(GetThumbnailResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -73,12 +73,12 @@ func (c *thumbnailService) GetThumbnail(ctx context.Context, in *GetRequest, opt
|
||||
|
||||
type ThumbnailServiceHandler interface {
|
||||
// Generates the thumbnail and returns it.
|
||||
GetThumbnail(context.Context, *GetRequest, *GetResponse) error
|
||||
GetThumbnail(context.Context, *GetThumbnailRequest, *GetThumbnailResponse) error
|
||||
}
|
||||
|
||||
func RegisterThumbnailServiceHandler(s server.Server, hdlr ThumbnailServiceHandler, opts ...server.HandlerOption) error {
|
||||
type thumbnailService interface {
|
||||
GetThumbnail(ctx context.Context, in *GetRequest, out *GetResponse) error
|
||||
GetThumbnail(ctx context.Context, in *GetThumbnailRequest, out *GetThumbnailResponse) error
|
||||
}
|
||||
type ThumbnailService struct {
|
||||
thumbnailService
|
||||
@@ -91,6 +91,6 @@ type thumbnailServiceHandler struct {
|
||||
ThumbnailServiceHandler
|
||||
}
|
||||
|
||||
func (h *thumbnailServiceHandler) GetThumbnail(ctx context.Context, in *GetRequest, out *GetResponse) error {
|
||||
func (h *thumbnailServiceHandler) GetThumbnail(ctx context.Context, in *GetThumbnailRequest, out *GetThumbnailResponse) error {
|
||||
return h.ThumbnailServiceHandler.GetThumbnail(ctx, in, out)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"image"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -46,17 +44,17 @@ func init() {
|
||||
if err != nil {
|
||||
log.Fatalf("could not register ThumbnailHandler: %v", err)
|
||||
}
|
||||
service.Server().Start()
|
||||
if err := service.Server().Start(); err != nil {
|
||||
log.Fatalf("could not start server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetThumbnailInvalidImage(t *testing.T) {
|
||||
req := proto.GetRequest{
|
||||
req := proto.GetThumbnailRequest{
|
||||
Filepath: "invalid.png",
|
||||
Filetype: proto.GetRequest_PNG,
|
||||
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
ThumbnailType: proto.GetThumbnailRequest_PNG,
|
||||
Height: 32,
|
||||
Width: 32,
|
||||
Username: "user1",
|
||||
}
|
||||
client := service.Client()
|
||||
cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client)
|
||||
@@ -65,26 +63,24 @@ func TestGetThumbnailInvalidImage(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestGetThumbnail(t *testing.T) {
|
||||
req := proto.GetRequest{
|
||||
Filepath: "oc.png",
|
||||
Filetype: proto.GetRequest_PNG,
|
||||
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
Height: 32,
|
||||
Width: 32,
|
||||
Authorization: "Bearer eyJhbGciOiJQUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwaG9lbml4IiwiZXhwIjoxNTkwNTc1Mzk4LCJqdGkiOiJqUEw5c1A3UUEzY0diYi1yRnhkSjJCWnFPc1BDTDg1ZyIsImlhdCI6MTU5MDU3NDc5OCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6OTIwMCIsInN1YiI6Ilh0U2lfbWl5V1NCLXBrdkdueFBvQzVBNGZsaWgwVUNMZ3ZVN2NMd2ptakNLWDdGWW4ySFdrNnJSQ0V1eTJHNXFBeV95TVFjX0ZLOWFORmhVTXJYMnBRQGtvbm5lY3QiLCJrYy5pc0FjY2Vzc1Rva2VuIjp0cnVlLCJrYy5hdXRob3JpemVkU2NvcGVzIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCJdLCJrYy5pZGVudGl0eSI6eyJrYy5pLmRuIjoiRWluc3RlaW4iLCJrYy5pLmlkIjoiY249ZWluc3RlaW4sb3U9dXNlcnMsZGM9ZXhhbXBsZSxkYz1vcmciLCJrYy5pLnVuIjoiZWluc3RlaW4ifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWxkYXAifQ.FSDe4vzwYpHbNfckBON5EI-01MS_dYFxenddqfJPzjlAEMEH2FFn2xQHCsxhC7wSxivhjV7Z5eRoNUR606keA64Tjs8pJBNECSptBMmE_xfAlc6X5IFILgDnR5bBu6Z2hhu-dVj72Hcyvo_X__OeWekYu7oyoXW41Mw3ayiUAwjCAzV3WPOAJ_r0zbW68_m29BgH3BoSxaF6lmjStIIAIyw7IBZ2QXb_FvGouknmfeWlGL9lkFPGL_dYKwjWieG947nY4Kg8IvHByEbw-xlY3L2EdA7Q8ZMbqdX7GzjtEIVYvCT4-TxWRcmB3SmO-Z8CVq27NHlKm3aZ0k2PS8Ga1w",
|
||||
Username: "user1",
|
||||
}
|
||||
client := service.Client()
|
||||
cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client)
|
||||
rsp, err := cl.GetThumbnail(context.Background(), &req)
|
||||
if err != nil {
|
||||
log.Fatalf("error %s", err.Error())
|
||||
}
|
||||
assert.NotEmpty(t, rsp.GetThumbnail())
|
||||
|
||||
img, _, _ := image.Decode(bytes.NewReader(rsp.GetThumbnail()))
|
||||
assert.Equal(t, 32, img.Bounds().Size().X)
|
||||
|
||||
assert.Equal(t, "image/png", rsp.GetMimetype())
|
||||
}
|
||||
// TODO(corby) update tests
|
||||
//func TestGetThumbnail(t *testing.T) {
|
||||
// req := proto.GetThumbnailRequest{
|
||||
// Filepath: "oc.png",
|
||||
// ThumbnailType: proto.GetThumbnailRequest_PNG,
|
||||
// Height: 32,
|
||||
// Width: 32,
|
||||
// }
|
||||
// client := service.Client()
|
||||
// cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client)
|
||||
// rsp, err := cl.GetThumbnail(context.Background(), &req)
|
||||
// if err != nil {
|
||||
// log.Fatalf("error %s", err.Error())
|
||||
// }
|
||||
// assert.NotEmpty(t, rsp.GetThumbnail())
|
||||
//
|
||||
// img, _, _ := image.Decode(bytes.NewReader(rsp.GetThumbnail()))
|
||||
// assert.Equal(t, 32, img.Bounds().Size().X)
|
||||
//
|
||||
// assert.Equal(t, "image/png", rsp.GetMimetype())
|
||||
//}
|
||||
|
||||
@@ -10,19 +10,18 @@ import (
|
||||
type TestRequest struct {
|
||||
testDataName string
|
||||
filepath string
|
||||
filetype proto.GetRequest_FileType
|
||||
etag string
|
||||
filetype proto.GetThumbnailRequest_ThumbnailType
|
||||
Checksum string
|
||||
width int32
|
||||
height int32
|
||||
authorization string
|
||||
expected proto.GetRequest
|
||||
expected proto.GetThumbnailRequest
|
||||
}
|
||||
|
||||
type TestResponse struct {
|
||||
testDataName string
|
||||
img []byte
|
||||
mimetype string
|
||||
expected proto.GetResponse
|
||||
expected proto.GetThumbnailResponse
|
||||
}
|
||||
|
||||
func TestRequestString(t *testing.T) {
|
||||
@@ -31,64 +30,53 @@ func TestRequestString(t *testing.T) {
|
||||
{
|
||||
"ASCII",
|
||||
"Foo.jpg",
|
||||
proto.GetRequest_JPG,
|
||||
proto.GetThumbnailRequest_JPG,
|
||||
"33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
24,
|
||||
24,
|
||||
"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
proto.GetRequest{
|
||||
proto.GetThumbnailRequest{
|
||||
Filepath: "Foo.jpg",
|
||||
Filetype: proto.GetRequest_JPG,
|
||||
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
ThumbnailType: proto.GetThumbnailRequest_JPG,
|
||||
Width: 24,
|
||||
Height: 24,
|
||||
Authorization: "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
},
|
||||
},
|
||||
{
|
||||
"UTF",
|
||||
"मिलन.jpg",
|
||||
proto.GetRequest_JPG,
|
||||
proto.GetThumbnailRequest_JPG,
|
||||
"33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
24,
|
||||
24,
|
||||
"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
proto.GetRequest{
|
||||
proto.GetThumbnailRequest{
|
||||
Filepath: "\340\244\256\340\244\277\340\244\262\340\244\250.jpg",
|
||||
Filetype: proto.GetRequest_JPG,
|
||||
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
ThumbnailType: proto.GetThumbnailRequest_JPG,
|
||||
Width: 24,
|
||||
Height: 24,
|
||||
Authorization: "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
},
|
||||
},
|
||||
{
|
||||
"PNG",
|
||||
"Foo.png",
|
||||
proto.GetRequest_PNG,
|
||||
proto.GetThumbnailRequest_PNG,
|
||||
"33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
24,
|
||||
24,
|
||||
"Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
proto.GetRequest{
|
||||
Filepath: "Foo.png",
|
||||
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
|
||||
Width: 24,
|
||||
Height: 24,
|
||||
Authorization: "Basic SGVXaG9SZWFkc1RoaXM6SXNTdHVwaWQK",
|
||||
proto.GetThumbnailRequest{
|
||||
Filepath: "Foo.png",
|
||||
Width: 24,
|
||||
Height: 24,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.testDataName, func(t *testing.T) {
|
||||
req := proto.GetRequest{
|
||||
req := proto.GetThumbnailRequest{
|
||||
Filepath: testCase.filepath,
|
||||
Filetype: testCase.filetype,
|
||||
Etag: testCase.etag,
|
||||
ThumbnailType: testCase.filetype,
|
||||
Height: testCase.height,
|
||||
Width: testCase.width,
|
||||
Authorization: testCase.authorization,
|
||||
}
|
||||
assert.Equal(t, testCase.expected.String(), req.String())
|
||||
})
|
||||
@@ -101,7 +89,7 @@ func TestResponseString(t *testing.T) {
|
||||
"ASCII",
|
||||
[]byte("image data"),
|
||||
"image/png",
|
||||
proto.GetResponse{
|
||||
proto.GetThumbnailResponse{
|
||||
Thumbnail: []byte("image data"),
|
||||
Mimetype: "image/png",
|
||||
},
|
||||
@@ -110,7 +98,7 @@ func TestResponseString(t *testing.T) {
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.testDataName, func(t *testing.T) {
|
||||
response := proto.GetResponse{
|
||||
response := proto.GetThumbnailResponse{
|
||||
Thumbnail: testCase.img,
|
||||
Mimetype: testCase.mimetype,
|
||||
}
|
||||
|
||||
@@ -1,68 +1,84 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "pkg/proto/v0;proto";
|
||||
|
||||
package com.owncloud.ocis.thumbnails.v0;
|
||||
|
||||
option go_package = "github.com/owncloud/ocis/thumbnails/pkg/proto/v0;proto";
|
||||
|
||||
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||
info: {
|
||||
title: "ownCloud Infinite Scale thumbnails";
|
||||
version: "1.0.0";
|
||||
contact: {
|
||||
name: "ownCloud GmbH";
|
||||
url: "https://github.com/owncloud/ocis";
|
||||
email: "support@owncloud.com";
|
||||
};
|
||||
license: {
|
||||
name: "Apache-2.0";
|
||||
url: "https://github.com/owncloud/ocis/blob/master/LICENSE";
|
||||
};
|
||||
};
|
||||
schemes: HTTP;
|
||||
schemes: HTTPS;
|
||||
consumes: "application/json";
|
||||
produces: "application/json";
|
||||
external_docs: {
|
||||
description: "Developer Manual";
|
||||
url: "https://owncloud.github.io/extensions/thumbnails/";
|
||||
};
|
||||
};
|
||||
info: {
|
||||
title: "ownCloud Infinite Scale thumbnails";
|
||||
version: "1.0.0";
|
||||
contact: {
|
||||
name: "ownCloud GmbH";
|
||||
url: "https://github.com/owncloud/ocis";
|
||||
email: "support@owncloud.com";
|
||||
};
|
||||
license: {
|
||||
name: "Apache-2.0";
|
||||
url: "https://github.com/owncloud/ocis/blob/master/LICENSE";
|
||||
};
|
||||
};
|
||||
schemes: HTTP;
|
||||
schemes: HTTPS;
|
||||
consumes: "application/json";
|
||||
produces: "application/json";
|
||||
external_docs: {
|
||||
description: "Developer Manual";
|
||||
url: "https://owncloud.dev/extensions/thumbnails/";
|
||||
};
|
||||
};
|
||||
|
||||
// A Service for handling thumbnail generation
|
||||
service ThumbnailService {
|
||||
// Generates the thumbnail and returns it.
|
||||
rpc GetThumbnail(GetRequest) returns (GetResponse);
|
||||
rpc GetThumbnail(GetThumbnailRequest) returns (GetThumbnailResponse);
|
||||
}
|
||||
|
||||
// A request to retrieve a thumbnail
|
||||
message GetRequest {
|
||||
message GetThumbnailRequest {
|
||||
// The path to the source image
|
||||
string filepath = 1;
|
||||
// The file types to which the thumbnail cna get encoded to.
|
||||
enum FileType {
|
||||
// The file types to which the thumbnail can get encoded to.
|
||||
enum ThumbnailType {
|
||||
PNG = 0; // Represents PNG type
|
||||
JPG = 1; // Represents JPG type
|
||||
}
|
||||
// The type to which the thumbnail should get encoded to.
|
||||
FileType filetype = 2;
|
||||
// The etag of the source image
|
||||
string etag = 3;
|
||||
ThumbnailType thumbnail_type = 2;
|
||||
// The width of the thumbnail
|
||||
int32 width = 4;
|
||||
int32 width = 3;
|
||||
// The height of the thumbnail
|
||||
int32 height = 5;
|
||||
// The authorization token
|
||||
string authorization = 6;
|
||||
// The user requesting the resource.
|
||||
string username = 7;
|
||||
int32 height = 4;
|
||||
oneof source {
|
||||
WebdavSource webdav_source = 5;
|
||||
CS3Source cs3_source = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message WebdavSource {
|
||||
// REQUIRED.
|
||||
string url = 1;
|
||||
// REQUIRED.
|
||||
bool is_public_link = 2;
|
||||
// OPTIONAL.
|
||||
string webdav_authorization = 3;
|
||||
// OPTIONAL.
|
||||
string reva_authorization = 4;
|
||||
// OPTIONAL.
|
||||
string public_link_token = 5;
|
||||
}
|
||||
|
||||
message CS3Source {
|
||||
string path = 1;
|
||||
string authorization = 2;
|
||||
}
|
||||
|
||||
// The service response
|
||||
message GetResponse {
|
||||
message GetThumbnailResponse {
|
||||
// The thumbnail as a binary
|
||||
bytes thumbnail = 1;
|
||||
// The mimetype of the thumbnail
|
||||
string mimetype = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@
|
||||
],
|
||||
"paths": {},
|
||||
"definitions": {
|
||||
"GetRequestFileType": {
|
||||
"GetThumbnailRequestThumbnailType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"PNG",
|
||||
"JPG"
|
||||
],
|
||||
"default": "PNG",
|
||||
"description": "The file types to which the thumbnail cna get encoded to."
|
||||
"description": "The file types to which the thumbnail can get encoded to."
|
||||
},
|
||||
"protobufAny": {
|
||||
"type": "object",
|
||||
@@ -69,7 +69,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"v0GetResponse": {
|
||||
"v0CS3Source": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"authorization": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v0GetThumbnailResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"thumbnail": {
|
||||
@@ -83,10 +94,35 @@
|
||||
}
|
||||
},
|
||||
"title": "The service response"
|
||||
},
|
||||
"v0WebdavSource": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "REQUIRED."
|
||||
},
|
||||
"isPublicLink": {
|
||||
"type": "boolean",
|
||||
"description": "REQUIRED."
|
||||
},
|
||||
"webdavAuthorization": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL."
|
||||
},
|
||||
"revaAuthorization": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL."
|
||||
},
|
||||
"publicLinkToken": {
|
||||
"type": "string",
|
||||
"description": "OPTIONAL."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"description": "Developer Manual",
|
||||
"url": "https://owncloud.github.io/extensions/thumbnails/"
|
||||
"url": "https://owncloud.dev/extensions/thumbnails/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +39,8 @@ func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, http.StatusText(http.StatusOK))
|
||||
if _, err := io.WriteString(w, http.StatusText(http.StatusOK)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"github.com/cs3org/reva/pkg/rgrpc/todo/pool"
|
||||
"github.com/owncloud/ocis/ocis-pkg/service/grpc"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/proto/v0"
|
||||
svc "github.com/owncloud/ocis/thumbnails/pkg/service/v0"
|
||||
@@ -23,19 +24,26 @@ func NewService(opts ...Option) grpc.Service {
|
||||
grpc.Flags(options.Flags...),
|
||||
grpc.Version(options.Config.Server.Version),
|
||||
)
|
||||
|
||||
tconf := options.Config.Thumbnail
|
||||
gc, err := pool.GetGatewayServiceClient(tconf.RevaGateway)
|
||||
if err != nil {
|
||||
options.Logger.Error().Err(err).Msg("could not get gateway client")
|
||||
return grpc.Service{}
|
||||
}
|
||||
var thumbnail proto.ThumbnailServiceHandler
|
||||
{
|
||||
thumbnail = svc.NewService(
|
||||
svc.Config(options.Config),
|
||||
svc.Logger(options.Logger),
|
||||
svc.ThumbnailSource(imgsource.NewWebDavSource(options.Config.Thumbnail.WebDavSource)),
|
||||
svc.ThumbnailSource(imgsource.NewWebDavSource(tconf)),
|
||||
svc.ThumbnailStorage(
|
||||
storage.NewFileSystemStorage(
|
||||
options.Config.Thumbnail.FileSystemStorage,
|
||||
tconf.FileSystemStorage,
|
||||
options.Logger,
|
||||
),
|
||||
),
|
||||
svc.CS3Source(imgsource.NewCS3Source(gc)),
|
||||
svc.CS3Client(gc),
|
||||
)
|
||||
thumbnail = svc.NewInstrument(thumbnail, options.Metrics)
|
||||
thumbnail = svc.NewLogging(thumbnail, options.Logger)
|
||||
|
||||
@@ -22,7 +22,7 @@ type instrument struct {
|
||||
}
|
||||
|
||||
// GetThumbnail implements the ThumbnailServiceHandler interface.
|
||||
func (i instrument) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rsp *v0proto.GetResponse) error {
|
||||
func (i instrument) GetThumbnail(ctx context.Context, req *v0proto.GetThumbnailRequest, rsp *v0proto.GetThumbnailResponse) error {
|
||||
timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) {
|
||||
us := v * 1000_000
|
||||
i.metrics.Latency.WithLabelValues().Observe(us)
|
||||
|
||||
@@ -22,7 +22,7 @@ type logging struct {
|
||||
}
|
||||
|
||||
// GetThumbnail implements the ThumbnailServiceHandler interface.
|
||||
func (l logging) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rsp *v0proto.GetResponse) error {
|
||||
func (l logging) GetThumbnail(ctx context.Context, req *v0proto.GetThumbnailRequest, rsp *v0proto.GetThumbnailResponse) error {
|
||||
start := time.Now()
|
||||
err := l.next.GetThumbnail(ctx, req, rsp)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
@@ -19,6 +20,8 @@ type Options struct {
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
ThumbnailStorage storage.Storage
|
||||
ImageSource imgsource.Source
|
||||
CS3Source imgsource.Source
|
||||
CS3Client gateway.GatewayAPIClient
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -66,3 +69,15 @@ func ThumbnailSource(val imgsource.Source) Option {
|
||||
o.ImageSource = val
|
||||
}
|
||||
}
|
||||
|
||||
func CS3Source(val imgsource.Source) Option {
|
||||
return func(o *Options) {
|
||||
o.CS3Source = val
|
||||
}
|
||||
}
|
||||
|
||||
func CS3Client(c gateway.GatewayAPIClient) Option {
|
||||
return func(o *Options) {
|
||||
o.CS3Client = c
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,22 @@ package svc
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
merrors "github.com/asim/go-micro/v3/errors"
|
||||
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"
|
||||
"github.com/cs3org/reva/pkg/token"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/preprocessor"
|
||||
v0proto "github.com/owncloud/ocis/thumbnails/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/thumbnail"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/thumbnail/imgsource"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
@@ -20,14 +30,17 @@ func NewService(opts ...Option) v0proto.ThumbnailServiceHandler {
|
||||
logger.Fatal().Err(err).Msg("resolutions not configured correctly")
|
||||
}
|
||||
svc := Thumbnail{
|
||||
serviceID: options.Config.Server.Namespace + "." + options.Config.Server.Name,
|
||||
serviceID: options.Config.Server.Namespace + "." + options.Config.Server.Name,
|
||||
webdavNamespace: options.Config.Thumbnail.WebdavNamespace,
|
||||
manager: thumbnail.NewSimpleManager(
|
||||
resolutions,
|
||||
options.ThumbnailStorage,
|
||||
logger,
|
||||
),
|
||||
source: options.ImageSource,
|
||||
logger: logger,
|
||||
webdavSource: options.ImageSource,
|
||||
cs3Source: options.CS3Source,
|
||||
logger: logger,
|
||||
cs3Client: options.CS3Client,
|
||||
}
|
||||
|
||||
return svc
|
||||
@@ -35,57 +48,190 @@ func NewService(opts ...Option) v0proto.ThumbnailServiceHandler {
|
||||
|
||||
// Thumbnail implements the GRPC handler.
|
||||
type Thumbnail struct {
|
||||
serviceID string
|
||||
manager thumbnail.Manager
|
||||
source imgsource.Source
|
||||
logger log.Logger
|
||||
serviceID string
|
||||
webdavNamespace string
|
||||
manager thumbnail.Manager
|
||||
webdavSource imgsource.Source
|
||||
cs3Source imgsource.Source
|
||||
logger log.Logger
|
||||
cs3Client gateway.GatewayAPIClient
|
||||
}
|
||||
|
||||
// GetThumbnail retrieves a thumbnail for an image
|
||||
func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rsp *v0proto.GetResponse) error {
|
||||
encoder := thumbnail.EncoderForType(req.Filetype.String())
|
||||
func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetThumbnailRequest, rsp *v0proto.GetThumbnailResponse) error {
|
||||
_, ok := v0proto.GetThumbnailRequest_ThumbnailType_value[req.ThumbnailType.String()]
|
||||
if !ok {
|
||||
g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type")
|
||||
return nil
|
||||
}
|
||||
encoder := thumbnail.EncoderForType(req.ThumbnailType.String())
|
||||
if encoder == nil {
|
||||
g.logger.Debug().Str("filetype", req.Filetype.String()).Msg("unsupported filetype")
|
||||
g.logger.Debug().Str("thumbnail_type", req.ThumbnailType.String()).Msg("unsupported thumbnail type")
|
||||
return nil
|
||||
}
|
||||
|
||||
auth := req.Authorization
|
||||
if auth == "" {
|
||||
return merrors.BadRequest(g.serviceID, "authorization is missing")
|
||||
var thumb []byte
|
||||
var err error
|
||||
switch {
|
||||
case req.GetWebdavSource() != nil:
|
||||
thumb, err = g.handleWebdavSource(ctx, req, encoder)
|
||||
case req.GetCs3Source() != nil:
|
||||
thumb, err = g.handleCS3Source(ctx, req, encoder)
|
||||
default:
|
||||
g.logger.Error().Msg("no image source provided")
|
||||
return merrors.BadRequest(g.serviceID, "image source is missing")
|
||||
}
|
||||
username := req.Username
|
||||
if username == "" {
|
||||
return merrors.BadRequest(g.serviceID, "username missing in request")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rsp.Thumbnail = thumb
|
||||
rsp.Mimetype = encoder.MimeType()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g Thumbnail) handleCS3Source(ctx context.Context, req *v0proto.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) {
|
||||
src := req.GetCs3Source()
|
||||
sRes, err := g.stat(src.Path, src.Authorization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr := thumbnail.Request{
|
||||
Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)),
|
||||
Encoder: encoder,
|
||||
ETag: req.Etag,
|
||||
Username: username,
|
||||
Checksum: sRes.GetInfo().GetChecksum().GetSum(),
|
||||
}
|
||||
|
||||
thumbnail := g.manager.GetStored(tr)
|
||||
if thumbnail != nil {
|
||||
rsp.Thumbnail = thumbnail
|
||||
rsp.Mimetype = tr.Encoder.MimeType()
|
||||
return nil
|
||||
thumb, ok := g.manager.Get(tr)
|
||||
if ok {
|
||||
return thumb, nil
|
||||
}
|
||||
|
||||
sCtx := imgsource.ContextSetAuthorization(ctx, auth)
|
||||
img, err := g.source.Get(sCtx, req.Filepath)
|
||||
ctx = imgsource.ContextSetAuthorization(ctx, src.Authorization)
|
||||
r, err := g.cs3Source.Get(ctx, src.Path)
|
||||
if err != nil {
|
||||
return merrors.InternalServerError(g.serviceID, "could not get image from source: %v", err.Error())
|
||||
return nil, merrors.InternalServerError(g.serviceID, "could not get image from source: %s", err.Error())
|
||||
}
|
||||
if img == nil {
|
||||
return merrors.InternalServerError(g.serviceID, "could not get image from source")
|
||||
defer r.Close() // nolint:errcheck
|
||||
pp := preprocessor.ForType(sRes.GetInfo().GetMimeType())
|
||||
img, err := pp.Convert(r)
|
||||
if img == nil || err != nil {
|
||||
return nil, merrors.InternalServerError(g.serviceID, "could not get image")
|
||||
}
|
||||
thumbnail, err = g.manager.Get(tr, img)
|
||||
if err != nil {
|
||||
return err
|
||||
if thumb, err = g.manager.Generate(tr, img); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsp.Thumbnail = thumbnail
|
||||
rsp.Mimetype = tr.Encoder.MimeType()
|
||||
return nil
|
||||
return thumb, nil
|
||||
}
|
||||
|
||||
func (g Thumbnail) handleWebdavSource(ctx context.Context, req *v0proto.GetThumbnailRequest, encoder thumbnail.Encoder) ([]byte, error) {
|
||||
src := req.GetWebdavSource()
|
||||
imgURL, err := url.Parse(src.Url)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "source url is invalid")
|
||||
}
|
||||
|
||||
var auth, statPath string
|
||||
if src.IsPublicLink {
|
||||
q := imgURL.Query()
|
||||
var rsp *gateway.AuthenticateResponse
|
||||
if q.Get("signature") != "" && q.Get("expiration") != "" {
|
||||
// Handle pre-signed public links
|
||||
sig := q.Get("signature")
|
||||
exp := q.Get("expiration")
|
||||
rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{
|
||||
Type: "publicshares",
|
||||
ClientId: src.PublicLinkToken,
|
||||
ClientSecret: strings.Join([]string{"signature", sig, exp}, "|"),
|
||||
})
|
||||
} else {
|
||||
rsp, err = g.cs3Client.Authenticate(ctx, &gateway.AuthenticateRequest{
|
||||
Type: "publicshares",
|
||||
ClientId: src.PublicLinkToken,
|
||||
// We pass an empty password because we expect non pre-signed public links
|
||||
// to not be password protected
|
||||
ClientSecret: "password|",
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, merrors.InternalServerError(g.serviceID, "could not authenticate: %s", err.Error())
|
||||
}
|
||||
auth = rsp.Token
|
||||
statPath = path.Join("/public", src.PublicLinkToken, req.Filepath)
|
||||
} else {
|
||||
auth = src.RevaAuthorization
|
||||
statPath = path.Join(g.webdavNamespace, req.Filepath)
|
||||
}
|
||||
sRes, err := g.stat(statPath, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := thumbnail.Request{
|
||||
Resolution: image.Rect(0, 0, int(req.Width), int(req.Height)),
|
||||
Encoder: encoder,
|
||||
Checksum: sRes.GetInfo().GetChecksum().GetSum(),
|
||||
}
|
||||
thumb, ok := g.manager.Get(tr)
|
||||
if ok {
|
||||
return thumb, nil
|
||||
}
|
||||
|
||||
if src.WebdavAuthorization != "" {
|
||||
ctx = imgsource.ContextSetAuthorization(ctx, src.WebdavAuthorization)
|
||||
}
|
||||
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())
|
||||
}
|
||||
defer r.Close() // nolint:errcheck
|
||||
pp := preprocessor.ForType(sRes.GetInfo().GetMimeType())
|
||||
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 thumb, nil
|
||||
}
|
||||
|
||||
func (g Thumbnail) stat(path, auth string) (*provider.StatResponse, error) {
|
||||
ctx := metadata.AppendToOutgoingContext(context.Background(), token.TokenHeader, auth)
|
||||
|
||||
req := &provider.StatRequest{
|
||||
Ref: &provider.Reference{
|
||||
Path: path,
|
||||
},
|
||||
}
|
||||
rsp, err := g.cs3Client.Stat(ctx, req)
|
||||
if err != nil {
|
||||
g.logger.Error().Err(err).Str("path", path).Msg("could not stat file")
|
||||
return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", err.Error())
|
||||
}
|
||||
|
||||
if rsp.Status.Code != rpc.Code_CODE_OK {
|
||||
switch rsp.Status.Code {
|
||||
case rpc.Code_CODE_NOT_FOUND:
|
||||
return nil, merrors.NotFound(g.serviceID, "could not stat file: %s", rsp.Status.Message)
|
||||
default:
|
||||
g.logger.Error().Str("status_message", rsp.Status.Message).Str("path", path).Msg("could not stat file")
|
||||
return nil, merrors.InternalServerError(g.serviceID, "could not stat file: %s", rsp.Status.Message)
|
||||
}
|
||||
}
|
||||
if rsp.Info.Type != provider.ResourceType_RESOURCE_TYPE_FILE {
|
||||
return nil, merrors.BadRequest(g.serviceID, "Unsupported file type")
|
||||
}
|
||||
if rsp.Info.GetChecksum().GetSum() == "" {
|
||||
g.logger.Error().Msg("resource info is missing checksum")
|
||||
return nil, merrors.NotFound(g.serviceID, "resource info is missing a checksum")
|
||||
}
|
||||
if !thumbnail.IsMimeTypeSupported(rsp.Info.MimeType) {
|
||||
return nil, merrors.NotFound(g.serviceID, "Unsupported file type")
|
||||
}
|
||||
return rsp, nil
|
||||
}
|
||||
|
||||
@@ -19,14 +19,13 @@ type tracing struct {
|
||||
}
|
||||
|
||||
// GetThumbnail implements the ThumbnailServiceHandler interface.
|
||||
func (t tracing) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rsp *v0proto.GetResponse) error {
|
||||
func (t tracing) GetThumbnail(ctx context.Context, req *v0proto.GetThumbnailRequest, rsp *v0proto.GetThumbnailResponse) error {
|
||||
ctx, span := trace.StartSpan(ctx, "Thumbnails.GetThumbnail")
|
||||
defer span.End()
|
||||
|
||||
span.Annotate([]trace.Attribute{
|
||||
trace.StringAttribute("filepath", req.Filepath),
|
||||
trace.StringAttribute("filetype", req.Filetype.String()),
|
||||
trace.StringAttribute("etag", req.Etag),
|
||||
trace.StringAttribute("thumbnail_type", req.ThumbnailType.String()),
|
||||
trace.Int64Attribute("width", int64(req.Width)),
|
||||
trace.Int64Attribute("height", int64(req.Height)),
|
||||
}, "Execute Thumbnails.GetThumbnail handler")
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package imgsource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
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"
|
||||
"github.com/cs3org/reva/pkg/rhttp"
|
||||
"github.com/cs3org/reva/pkg/token"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type CS3 struct {
|
||||
client gateway.GatewayAPIClient
|
||||
}
|
||||
|
||||
func NewCS3Source(c gateway.GatewayAPIClient) CS3 {
|
||||
return CS3{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
// Get downloads the file from a cs3 service
|
||||
// The caller MUST make sure to close the returned ReadCloser
|
||||
func (s CS3) Get(ctx context.Context, path string) (io.ReadCloser, error) {
|
||||
auth, ok := ContextGetAuthorization(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("cs3source: authorization missing")
|
||||
}
|
||||
ctx = metadata.AppendToOutgoingContext(context.Background(), token.TokenHeader, auth)
|
||||
rsp, err := s.client.InitiateFileDownload(ctx, &provider.InitiateFileDownloadRequest{
|
||||
Ref: &provider.Reference{
|
||||
Path: path,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rsp.Status.Code != rpc.Code_CODE_OK {
|
||||
return nil, fmt.Errorf("could not load image: %s", rsp.Status.Message)
|
||||
}
|
||||
var ep, tk string
|
||||
for _, p := range rsp.Protocols {
|
||||
if p.Protocol == "simple" {
|
||||
ep, tk = p.DownloadEndpoint, p.Token
|
||||
}
|
||||
}
|
||||
|
||||
httpReq, err := rhttp.NewRequest(ctx, "GET", ep, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpReq.Header.Set(token.TokenHeader, auth)
|
||||
httpReq.Header.Set("X-REVA-TRANSFER", tk)
|
||||
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec
|
||||
client := &http.Client{}
|
||||
|
||||
resp, err := client.Do(httpReq) // nolint:bodyclose
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", path, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package imgsource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -23,17 +23,12 @@ type FileSystem struct {
|
||||
}
|
||||
|
||||
// Get retrieves an image from the filesystem.
|
||||
func (s FileSystem) Get(ctx context.Context, file string) (image.Image, error) {
|
||||
func (s FileSystem) Get(ctx context.Context, file string) (io.ReadCloser, error) {
|
||||
imgPath := filepath.Join(s.basePath, file)
|
||||
f, err := os.Open(filepath.Clean(imgPath))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to load the file %s from %s", file, imgPath)
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Get: Decode:")
|
||||
}
|
||||
|
||||
return img, nil
|
||||
return f, nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package imgsource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
"io"
|
||||
)
|
||||
|
||||
type key int
|
||||
@@ -13,7 +13,7 @@ const (
|
||||
|
||||
// Source defines the interface for image sources
|
||||
type Source interface {
|
||||
Get(ctx context.Context, path string) (image.Image, error)
|
||||
Get(ctx context.Context, path string) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// ContextSetAuthorization puts the authorization in the context.
|
||||
|
||||
@@ -4,59 +4,50 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"image"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
_ "image/gif" // Import the gif package so that image.Decode can understand gifs
|
||||
_ "image/jpeg" // Import the jpeg package so that image.Decode can understand jpegs
|
||||
_ "image/png" // Import the png package so that image.Decode can understand pngs
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// NewWebDavSource creates a new webdav instance.
|
||||
func NewWebDavSource(cfg config.WebDavSource) WebDav {
|
||||
func NewWebDavSource(cfg config.Thumbnail) WebDav {
|
||||
return WebDav{
|
||||
baseURL: cfg.BaseURL,
|
||||
insecure: cfg.Insecure,
|
||||
insecure: cfg.WebdavAllowInsecure,
|
||||
}
|
||||
}
|
||||
|
||||
// WebDav implements the Source interface for webdav services
|
||||
type WebDav struct {
|
||||
baseURL string
|
||||
insecure bool
|
||||
}
|
||||
|
||||
// Get downloads the file from a webdav service
|
||||
func (s WebDav) Get(ctx context.Context, file string) (image.Image, error) {
|
||||
u, _ := url.Parse(s.baseURL)
|
||||
u.Path = path.Join(u.Path, file)
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
// The caller MUST make sure to close the returned ReadCloser
|
||||
func (s WebDav) Get(ctx context.Context, url string) (io.ReadCloser, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, `could not get the image "%s"`, file)
|
||||
return nil, errors.Wrapf(err, `could not get the image "%s"`, url)
|
||||
}
|
||||
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: s.insecure}
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: s.insecure} //nolint:gosec
|
||||
|
||||
auth, ok := ContextGetAuthorization(ctx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not get image \"%s\" error: authorization is missing", file)
|
||||
if auth, ok := ContextGetAuthorization(ctx); ok {
|
||||
req.Header.Add("Authorization", auth)
|
||||
}
|
||||
req.Header.Add("Authorization", auth)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
resp, err := client.Do(req) // nolint:bodyclose
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, `could not get the image "%s"`, file)
|
||||
return nil, errors.Wrapf(err, `could not get the image "%s"`, url)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", file, resp.StatusCode)
|
||||
return nil, fmt.Errorf("could not get the image \"%s\". Request returned with statuscode %d ", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(resp.Body)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, `could not decode the image "%s"`, file)
|
||||
}
|
||||
return img, nil
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
@@ -93,15 +93,6 @@ func (rs Resolutions) ClosestMatch(requested image.Rectangle, sourceSize image.R
|
||||
return match
|
||||
}
|
||||
|
||||
func mapRatio(given image.Rectangle, other image.Rectangle) image.Rectangle {
|
||||
isLandscape := given.Dx() > given.Dy()
|
||||
ratio := float64(given.Dx()) / float64(given.Dy())
|
||||
if isLandscape {
|
||||
return image.Rect(0, 0, other.Dx(), int(float64(other.Dx())/ratio))
|
||||
}
|
||||
return image.Rect(0, 0, int(float64(other.Dy())*ratio), other.Dy())
|
||||
}
|
||||
|
||||
func dimensionLength(rect image.Rectangle, isLandscape bool) int {
|
||||
if isLandscape {
|
||||
return rect.Dx()
|
||||
|
||||
@@ -93,8 +93,7 @@ func TestClosestMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseWithEmptyString(t *testing.T) {
|
||||
_, err := ParseResolution("")
|
||||
if err == nil {
|
||||
if _, err := ParseResolution(""); err == nil {
|
||||
t.Error("Parse with empty string should return an error.")
|
||||
}
|
||||
}
|
||||
@@ -120,20 +119,3 @@ func TestParseResolution(t *testing.T) {
|
||||
t.Errorf("Expected resolution %s got %s", rStr, r.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapRatio(t *testing.T) {
|
||||
testData := [][]image.Rectangle{
|
||||
{image.Rect(0, 0, 1920, 1080), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 32, 18)},
|
||||
{image.Rect(0, 0, 1080, 1920), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 18, 32)},
|
||||
{image.Rect(0, 0, 1024, 735), image.Rect(0, 0, 32, 32), image.Rect(0, 0, 32, 22)},
|
||||
}
|
||||
for _, row := range testData {
|
||||
given := row[0]
|
||||
other := row[1]
|
||||
expected := row[2]
|
||||
mapped := mapRatio(given, other)
|
||||
if mapped.Dx() != expected.Dx() || mapped.Dy() != expected.Dy() {
|
||||
t.Errorf("Expected %dx%d got %dx%d", expected.Dx(), expected.Dy(), mapped.Dx(), mapped.Dy())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
usersDir = "users"
|
||||
filesDir = "files"
|
||||
)
|
||||
|
||||
// NewFileSystemStorage creates a new instanz of FileSystem
|
||||
// NewFileSystemStorage creates a new instance of FileSystem
|
||||
func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) *FileSystem {
|
||||
return &FileSystem{
|
||||
root: cfg.RootDirectory,
|
||||
@@ -32,114 +25,56 @@ func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) *File
|
||||
type FileSystem struct {
|
||||
root string
|
||||
logger log.Logger
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// Get loads the image from the file system.
|
||||
func (s *FileSystem) Get(username string, key string) []byte {
|
||||
userDir := s.userDir(username)
|
||||
img := filepath.Join(userDir, key)
|
||||
content, err := ioutil.ReadFile(img)
|
||||
func (s *FileSystem) Get(key string) ([]byte, bool) {
|
||||
img := filepath.Join(s.root, filesDir, key)
|
||||
content, err := os.ReadFile(img)
|
||||
if err != nil {
|
||||
s.logger.Debug().Str("err", err.Error()).Str("key", key).Msg("could not load thumbnail from store")
|
||||
return nil
|
||||
if !os.IsNotExist(err) {
|
||||
s.logger.Debug().Str("err", err.Error()).Str("key", key).Msg("could not load thumbnail from store")
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
return content
|
||||
return content, true
|
||||
}
|
||||
|
||||
// Set writes the image to the file system.
|
||||
func (s *FileSystem) Set(username string, key string, img []byte) error {
|
||||
_, err := s.storeImage(key, img)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not store image")
|
||||
}
|
||||
userDir, err := s.createUserDir(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.linkImageToUserDir(key, userDir)
|
||||
}
|
||||
|
||||
// BuildKey generate the unique key for a thumbnail.
|
||||
// The key is structure as follows:
|
||||
//
|
||||
// <first two letters of etag>/<next two letters of etag>/<rest of etag>/<width>x<height>.<filetype>
|
||||
//
|
||||
// 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 {
|
||||
etag := r.ETag
|
||||
filetype := r.Types[0]
|
||||
filename := strconv.Itoa(r.Resolution.Dx()) + "x" + strconv.Itoa(r.Resolution.Dy()) + "." + filetype
|
||||
|
||||
return filepath.Join(etag[:2], etag[2:4], etag[4:], filename)
|
||||
}
|
||||
|
||||
func (s *FileSystem) rootDir(key string) string {
|
||||
p := strings.Split(key, string(os.PathSeparator))
|
||||
return p[0]
|
||||
}
|
||||
|
||||
func (s *FileSystem) storeImage(key string, img []byte) (string, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
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 {
|
||||
return "", errors.Wrapf(err, "error while creating directory %s", dir)
|
||||
return errors.Wrapf(err, "error while creating directory %s", dir)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(imgPath); os.IsNotExist(err) {
|
||||
f, err := os.Create(imgPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not create file \"%s\"", key)
|
||||
return errors.Wrapf(err, "could not create file \"%s\"", key)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Write(img)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "could not write to file \"%s\"", key)
|
||||
if _, err = f.Write(img); err != nil {
|
||||
return errors.Wrapf(err, "could not write to file \"%s\"", key)
|
||||
}
|
||||
}
|
||||
|
||||
return imgPath, nil
|
||||
}
|
||||
|
||||
// userDir returns the path to the user directory.
|
||||
// The username is hashed before appending it on the path to prevent bugs caused by invalid folder names.
|
||||
// Also the hash is then splitted up in three parts that results in a path which looks as follows:
|
||||
// <filestorage-root>/users/<3 characters>/<3 characters>/<48 characters>/
|
||||
// This will balance the folders in setups with many users.
|
||||
func (s *FileSystem) userDir(username string) string {
|
||||
hash := sha256.New224()
|
||||
hash.Write([]byte(username))
|
||||
unHash := hex.EncodeToString(hash.Sum(nil)) // 224 Bits or 224 / 4 = 56 characters.
|
||||
|
||||
return filepath.Join(s.root, usersDir, unHash[:3], unHash[3:6], unHash[6:])
|
||||
}
|
||||
|
||||
func (s *FileSystem) createUserDir(username string) (string, error) {
|
||||
userDir := s.userDir(username)
|
||||
if err := os.MkdirAll(userDir, 0700); err != nil {
|
||||
return "", errors.Wrapf(err, "could not create userDir: %s", userDir)
|
||||
}
|
||||
|
||||
return userDir, nil
|
||||
}
|
||||
|
||||
// linkImageToUserDir links the stored images to the user directory.
|
||||
// The goal is to minimize disk usage by linking to the images if they already exist and avoid file duplicaiton.
|
||||
func (s *FileSystem) linkImageToUserDir(key string, userDir string) error {
|
||||
imgRootDir := s.rootDir(key)
|
||||
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
err := os.Symlink(filepath.Join(s.root, filesDir, imgRootDir), filepath.Join(userDir, imgRootDir))
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return errors.Wrap(err, "could not link image to userdir")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildKey generate the unique key for a thumbnail.
|
||||
// The key is structure as follows:
|
||||
//
|
||||
// <first two letters of checksum>/<next two letters of checksum>/<rest of checksum>/<width>x<height>.<filetype>
|
||||
//
|
||||
// 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 {
|
||||
checksum := r.Checksum
|
||||
filetype := r.Types[0]
|
||||
filename := strconv.Itoa(r.Resolution.Dx()) + "x" + strconv.Itoa(r.Resolution.Dy()) + "." + filetype
|
||||
|
||||
return filepath.Join(checksum[:2], checksum[2:4], checksum[4:], filename)
|
||||
}
|
||||
|
||||
@@ -7,38 +7,31 @@ import (
|
||||
// NewInMemoryStorage creates a new InMemory instance.
|
||||
func NewInMemoryStorage() InMemory {
|
||||
return InMemory{
|
||||
store: make(map[string]map[string][]byte),
|
||||
store: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
// InMemory represents an in memory storage for thumbnails
|
||||
// Can be used during development
|
||||
type InMemory struct {
|
||||
store map[string]map[string][]byte
|
||||
store map[string][]byte
|
||||
}
|
||||
|
||||
// Get loads the thumbnail from memory.
|
||||
func (s InMemory) Get(username string, key string) []byte {
|
||||
userImages := s.store[username]
|
||||
if userImages == nil {
|
||||
return nil
|
||||
}
|
||||
return s.store[username][key]
|
||||
func (s InMemory) Get(key string) ([]byte, bool) {
|
||||
return s.store[key], true
|
||||
}
|
||||
|
||||
// Set stores the thumbnail in memory.
|
||||
func (s InMemory) Set(username string, key string, thumbnail []byte) error {
|
||||
if _, ok := s.store[username]; !ok {
|
||||
s.store[username] = make(map[string][]byte)
|
||||
}
|
||||
s.store[username][key] = thumbnail
|
||||
func (s InMemory) Put(key string, thumbnail []byte) error {
|
||||
s.store[key] = thumbnail
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildKey generates a unique key to store and retrieve the thumbnail.
|
||||
func (s InMemory) BuildKey(r Request) string {
|
||||
parts := []string{
|
||||
r.ETag,
|
||||
r.Checksum,
|
||||
r.Resolution.String(),
|
||||
strings.Join(r.Types, ","),
|
||||
}
|
||||
|
||||
@@ -6,14 +6,20 @@ import (
|
||||
|
||||
// Request combines different attributes needed for storage operations.
|
||||
type Request struct {
|
||||
ETag string
|
||||
// The checksum of the source file
|
||||
// Will be used to determine if a thumbnail exists
|
||||
Checksum string
|
||||
// Types provided by the encoder.
|
||||
// Contains the mimetypes of the thumbnail.
|
||||
// In case of jpg/jpeg it will contain both.
|
||||
Types []string
|
||||
// The resolution of the thumbnail
|
||||
Resolution image.Rectangle
|
||||
}
|
||||
|
||||
// Storage defines the interface for a thumbnail store.
|
||||
type Storage interface {
|
||||
Get(string, string) []byte
|
||||
Set(string, string, []byte) error
|
||||
Get(string) ([]byte, bool)
|
||||
Put(string, []byte) error
|
||||
BuildKey(Request) string
|
||||
}
|
||||
|
||||
@@ -2,28 +2,38 @@ package thumbnail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/thumbnail/storage"
|
||||
"golang.org/x/image/draw"
|
||||
"image"
|
||||
"mime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedMimeTypes = [...]string{
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"text/plain",
|
||||
}
|
||||
)
|
||||
|
||||
// Request bundles information needed to generate a thumbnail for afile
|
||||
type Request struct {
|
||||
Resolution image.Rectangle
|
||||
Encoder Encoder
|
||||
ETag string
|
||||
Username string
|
||||
Checksum string
|
||||
}
|
||||
|
||||
// Manager is responsible for generating thumbnails
|
||||
type Manager interface {
|
||||
// Get will return a thumbnail for a file
|
||||
Get(Request, image.Image) ([]byte, error)
|
||||
// GetStored loads the thumbnail from the storage.
|
||||
// Generate will return a thumbnail for a file
|
||||
Generate(Request, image.Image) ([]byte, error)
|
||||
// Get loads the thumbnail from the storage.
|
||||
// It will return nil if no image is stored for the given context.
|
||||
GetStored(Request) []byte
|
||||
Get(Request) ([]byte, bool)
|
||||
}
|
||||
|
||||
// NewSimpleManager creates a new instance of SimpleManager
|
||||
@@ -42,46 +52,54 @@ type SimpleManager struct {
|
||||
resolutions Resolutions
|
||||
}
|
||||
|
||||
// Get implements the Get Method of Manager
|
||||
func (s SimpleManager) Get(r Request, img image.Image) ([]byte, error) {
|
||||
// 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)
|
||||
|
||||
key := s.storage.BuildKey(mapToStorageRequest(r))
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
err := r.Encoder.Encode(buf, thumbnail)
|
||||
dst := new(bytes.Buffer)
|
||||
err := r.Encoder.Encode(dst, thumbnail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes := buf.Bytes()
|
||||
err = s.storage.Set(r.Username, key, bytes)
|
||||
|
||||
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")
|
||||
}
|
||||
return bytes, nil
|
||||
return dst.Bytes(), nil
|
||||
}
|
||||
|
||||
// GetStored tries to get the stored thumbnail and return it.
|
||||
// Get tries to get the stored thumbnail and return it.
|
||||
// If there is no cached thumbnail it will return nil
|
||||
func (s SimpleManager) GetStored(r Request) []byte {
|
||||
key := s.storage.BuildKey(mapToStorageRequest(r))
|
||||
stored := s.storage.Get(r.Username, key)
|
||||
return stored
|
||||
func (s SimpleManager) Get(r Request) ([]byte, bool) {
|
||||
k := s.storage.BuildKey(mapToStorageRequest(r))
|
||||
return s.storage.Get(k)
|
||||
}
|
||||
|
||||
func (s SimpleManager) generate(r image.Rectangle, img image.Image) image.Image {
|
||||
targetResolution := mapRatio(img.Bounds(), r)
|
||||
thumbnail := image.NewRGBA(targetResolution)
|
||||
draw.ApproxBiLinear.Scale(thumbnail, targetResolution, img, img.Bounds(), draw.Over, nil)
|
||||
return thumbnail
|
||||
return imaging.Thumbnail(img, r.Dx(), r.Dy(), imaging.Lanczos)
|
||||
}
|
||||
|
||||
func mapToStorageRequest(r Request) storage.Request {
|
||||
sR := storage.Request{
|
||||
ETag: r.ETag,
|
||||
return storage.Request{
|
||||
Checksum: r.Checksum,
|
||||
Resolution: r.Resolution,
|
||||
Types: r.Encoder.Types(),
|
||||
}
|
||||
return sR
|
||||
}
|
||||
|
||||
func IsMimeTypeSupported(m string) bool {
|
||||
mimeType, _, err := mime.ParseMediaType(m)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, mt := range SupportedMimeTypes {
|
||||
if strings.EqualFold(mt, mimeType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func BenchmarkGet(b *testing.B) {
|
||||
res, _ := ParseResolution("32x32")
|
||||
req := Request{
|
||||
Resolution: res,
|
||||
ETag: "1872ade88f3013edeb33decd74a4f947",
|
||||
Checksum: "1872ade88f3013edeb33decd74a4f947",
|
||||
}
|
||||
cwd, _ := os.Getwd()
|
||||
p := filepath.Join(cwd, "../../testdata/oc.png")
|
||||
@@ -42,6 +42,6 @@ func BenchmarkGet(b *testing.B) {
|
||||
img, ext, _ := image.Decode(f)
|
||||
req.Encoder = EncoderForType(ext)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sut.Get(req, img)
|
||||
_, _ = sut.Generate(req, img)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package tracing
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"contrib.go.opencensus.io/exporter/jaeger"
|
||||
"contrib.go.opencensus.io/exporter/ocagent"
|
||||
"contrib.go.opencensus.io/exporter/zipkin"
|
||||
openzipkin "github.com/openzipkin/zipkin-go"
|
||||
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/thumbnails/pkg/config"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
func Configure(cfg *config.Config, logger log.Logger) error {
|
||||
if cfg.Tracing.Enabled {
|
||||
switch t := cfg.Tracing.Type; t {
|
||||
case "agent":
|
||||
exporter, err := ocagent.NewExporter(
|
||||
ocagent.WithReconnectionPeriod(5*time.Second),
|
||||
ocagent.WithAddress(cfg.Tracing.Endpoint),
|
||||
ocagent.WithServiceName(cfg.Tracing.Service),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create agent tracing")
|
||||
return err
|
||||
}
|
||||
trace.RegisterExporter(exporter)
|
||||
view.RegisterExporter(exporter)
|
||||
case "jaeger":
|
||||
exporter, err := jaeger.NewExporter(
|
||||
jaeger.Options{
|
||||
AgentEndpoint: cfg.Tracing.Endpoint,
|
||||
CollectorEndpoint: cfg.Tracing.Collector,
|
||||
Process: jaeger.Process{
|
||||
ServiceName: cfg.Tracing.Service,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create jaeger tracing")
|
||||
return err
|
||||
}
|
||||
trace.RegisterExporter(exporter)
|
||||
case "zipkin":
|
||||
endpoint, err := openzipkin.NewEndpoint(
|
||||
cfg.Tracing.Service,
|
||||
cfg.Tracing.Endpoint,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().
|
||||
Err(err).
|
||||
Str("endpoint", cfg.Tracing.Endpoint).
|
||||
Str("collector", cfg.Tracing.Collector).
|
||||
Msg("Failed to create zipkin tracing")
|
||||
return err
|
||||
}
|
||||
exporter := zipkin.NewExporter(
|
||||
zipkinhttp.NewReporter(
|
||||
cfg.Tracing.Collector,
|
||||
),
|
||||
endpoint,
|
||||
)
|
||||
trace.RegisterExporter(exporter)
|
||||
default:
|
||||
logger.Warn().
|
||||
Str("type", t).
|
||||
Msg("Unknown tracing backend")
|
||||
}
|
||||
trace.ApplyConfig(
|
||||
trace.Config{
|
||||
DefaultSampler: trace.AlwaysSample(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
logger.Debug().
|
||||
Msg("Tracing is not enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user