first prototype of the thumbnail service

Currently uses in memory caching and loads the file from the local
filesystem.
This commit is contained in:
David Christofas
2020-03-03 16:40:09 +01:00
parent 883778d883
commit a98df0b11f
11 changed files with 214 additions and 15 deletions
+11
View File
@@ -0,0 +1,11 @@
package cache
import (
"image"
)
// Cache defines the interface for a thumbnail cache.
type Cache interface {
Get(key string) image.Image
Set(key string, thumbnail image.Image) (image.Image, error)
}
+3
View File
@@ -0,0 +1,3 @@
package cache
// TODO: implement filesystem cache for longer persistence
+22
View File
@@ -0,0 +1,22 @@
package cache
import "image"
func NewInMemoryCache() InMemoryCache {
return InMemoryCache{
store: make(map[string]image.Image),
}
}
type InMemoryCache struct {
store map[string]image.Image
}
func (fsc InMemoryCache) Get(key string) image.Image {
return fsc.store[key]
}
func (fsc InMemoryCache) Set(key string, thumbnail image.Image) (image.Image, error) {
fsc.store[key] = thumbnail
return thumbnail, nil
}
+47
View File
@@ -0,0 +1,47 @@
package thumbnails
import (
"image"
"image/jpeg"
"image/png"
"io"
"strings"
)
type Encoder interface {
Encode(io.Writer, image.Image) error
Types() []string
}
type PngEncoder struct{}
func (e PngEncoder) Encode(w io.Writer, i image.Image) error {
return png.Encode(w, i)
}
func (e PngEncoder) Types() []string {
return []string{"png"}
}
type JpegEncoder struct{}
func (e JpegEncoder) Encode(w io.Writer, i image.Image) error {
return jpeg.Encode(w, i, nil)
}
func (e JpegEncoder) Types() []string {
return []string{"jpeg", "jpg"}
}
func EncoderForType(fileType string) Encoder {
switch strings.ToLower(fileType) {
case "png":
return PngEncoder{}
case "jpg":
fallthrough
case "jpeg":
return JpegEncoder{}
default:
return nil
}
}
+86
View File
@@ -0,0 +1,86 @@
package thumbnails
import (
"bytes"
"image"
"os"
"strings"
"time"
"github.com/nfnt/resize"
"github.com/owncloud/ocis-thumbnails/pkg/thumbnails/cache"
)
// ThumbnailContext bundles information needed to generate a thumbnail for afile
type ThumbnailContext struct {
Width int
Height int
ImagePath string
Encoder Encoder
}
// Manager is responsible for generating thumbnails
type Manager interface {
// Get will return a thumbnail for a file
Get(ThumbnailContext) ([]byte, error)
GetCached(ThumbnailContext) []byte
}
// SimpleManager is a simple implementation of Manager
type SimpleManager struct {
Cache cache.Cache
}
// Get implements the Get Method of Manager
func (s SimpleManager) Get(ctx ThumbnailContext) ([]byte, error) {
key := buildCacheKey(ctx)
cached := s.Cache.Get(key)
if cached == nil {
thumbnail := s.generate(ctx)
s.Cache.Set(key, thumbnail)
cached = thumbnail
}
buf := new(bytes.Buffer)
err := ctx.Encoder.Encode(buf, cached)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// GetCached tries to get the cached thumbnail and return it.
// If there is no cached thumbnail it will return nil
func (s SimpleManager) GetCached(ctx ThumbnailContext) []byte {
key := buildCacheKey(ctx)
cached := s.Cache.Get(key)
if cached == nil {
return nil
}
buf := new(bytes.Buffer)
ctx.Encoder.Encode(buf, cached)
return buf.Bytes()
}
func (s SimpleManager) generate(ctx ThumbnailContext) image.Image {
// TODO: remove, just for demo purposes
time.Sleep(time.Second * 2)
// TODO: get file from reva
reader, _ := os.Open(ctx.ImagePath)
defer reader.Close()
m, _, _ := image.Decode(reader)
thumbnail := resize.Thumbnail(uint(ctx.Width), uint(ctx.Height), m, resize.Lanczos2)
return thumbnail
}
func buildCacheKey(ctx ThumbnailContext) string {
parts := []string{
ctx.ImagePath,
string(ctx.Width) + "x" + string(ctx.Height),
strings.Join(ctx.Encoder.Types(), ","),
}
return strings.Join(parts, "+")
}