mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-01 10:56:31 +08:00
fix (plugin): extend API for plugins
This commit is contained in:
@ -148,7 +148,12 @@ func (this Config) Interface() interface{} {
|
||||
}
|
||||
|
||||
func (this Config) save() {
|
||||
if this.path == nil {
|
||||
Log.Error("Config error")
|
||||
return
|
||||
}
|
||||
if gjson.Valid(this.json) == false {
|
||||
Log.Error("Config error")
|
||||
return
|
||||
}
|
||||
if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil {
|
||||
|
||||
@ -3,6 +3,7 @@ package common
|
||||
const (
|
||||
APP_VERSION = "v0.3"
|
||||
CONFIG_PATH = "data/config/"
|
||||
PLUGIN_PATH = "data/plugin/"
|
||||
COOKIE_NAME_AUTH = "auth"
|
||||
COOKIE_NAME_PROOF = "proof"
|
||||
COOKIE_PATH = "/api/"
|
||||
|
||||
21
server/common/debug.go
Normal file
21
server/common/debug.go
Normal file
@ -0,0 +1,21 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PrintMemUsage() {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
|
||||
fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc))
|
||||
fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc))
|
||||
fmt.Printf("\tSys = %v MiB", bToMb(m.Sys))
|
||||
fmt.Printf("\tObjects = %d", m.HeapObjects)
|
||||
fmt.Printf("\tNumGC = %v\n", m.NumGC)
|
||||
}
|
||||
|
||||
func bToMb(b uint64) uint64 {
|
||||
return b / 1024 / 1024
|
||||
}
|
||||
@ -1,11 +1,25 @@
|
||||
package common
|
||||
|
||||
type Plugin struct {
|
||||
Type string
|
||||
Call interface{}
|
||||
Priority int
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Register struct{}
|
||||
type Get struct{}
|
||||
|
||||
var Hooks = struct {
|
||||
Get Get
|
||||
Register Register
|
||||
}{
|
||||
Get: Get{},
|
||||
Register: Register{},
|
||||
}
|
||||
|
||||
const (
|
||||
PROCESS_FILE_CONTENT_BEFORE_SEND = "PROCESS_FILE_CONTENT_BEFORE_SEND"
|
||||
)
|
||||
var process_file_content_before_send []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)
|
||||
func (this Register) ProcessFileContentBeforeSend(fn func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)) {
|
||||
process_file_content_before_send = append(process_file_content_before_send, fn)
|
||||
}
|
||||
func (this Get) ProcessFileContentBeforeSend() []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) {
|
||||
return process_file_content_before_send
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package ctrl
|
||||
import (
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"github.com/mickael-kerjean/nuage/server/model"
|
||||
"github.com/mickael-kerjean/nuage/server/plugin"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
@ -114,10 +113,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
mType := GetMimeType(req.URL.Query().Get("path"))
|
||||
res.Header().Set("Content-Type", mType)
|
||||
|
||||
for _, obj := range plugin.ProcessFileContentBeforeSend() {
|
||||
if obj == nil {
|
||||
continue
|
||||
}
|
||||
for _, obj := range Hooks.Get.ProcessFileContentBeforeSend() {
|
||||
if file, err = obj(file, &ctx, &res, req); err != nil {
|
||||
SendErrorResult(res, err)
|
||||
return
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
go build -buildmode=plugin -o ../../../dist/data/plugin/example.so index.go
|
||||
@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Register(config *Config) []Plugin {
|
||||
config.Get("plugins.example.foo").Default("bar")
|
||||
|
||||
return []Plugin{
|
||||
{
|
||||
Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle
|
||||
Call: hook, // actual function we trigger
|
||||
Priority: 5, // determine execution order whilst multiple plugin type
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func hook(file io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||
Log.Debug("Plugin::Example")
|
||||
Log.Debug("Conf: plugins.example.foo = '%s'", ctx.Config.Get("plugins.example.foo").String())
|
||||
return file, nil
|
||||
}
|
||||
43
server/plugin/image_heavy/index.go
Normal file
43
server/plugin/image_heavy/index.go
Normal file
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"github.com/nfnt/resize"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Init(config *Config) {
|
||||
plugin_enable := config.Get("transcoder.image.enable").Default(true).Bool()
|
||||
plugin_thumbsize := uint(config.Get("transcoder.image.thumbnail_size").Default(300).Int())
|
||||
|
||||
Hooks.Register.ProcessFileContentBeforeSend(func(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||
if plugin_enable == false {
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
mType := GetMimeType(query.Get("path"))
|
||||
|
||||
if mType != "image/jpeg" {
|
||||
return reader, nil
|
||||
} else if query.Get("thumbnail") != "true" {
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
(*res).Header().Set("Cache-Control", "max-age=3600")
|
||||
img, err := jpeg.Decode(reader)
|
||||
if err != nil {
|
||||
return reader, nil
|
||||
}
|
||||
if obj, ok := reader.(interface{ Close() error }); ok {
|
||||
obj.Close()
|
||||
}
|
||||
img = resize.Resize(plugin_thumbsize, 0, img, resize.Lanczos3)
|
||||
out := bytes.NewBufferString("")
|
||||
jpeg.Encode(out, img, &jpeg.Options{50})
|
||||
return out, nil
|
||||
})
|
||||
}
|
||||
@ -15,95 +15,86 @@ const (
|
||||
ImageCachePath = "data/cache/image/"
|
||||
)
|
||||
|
||||
func Register(conf *Config) []Plugin {
|
||||
conf.Get("plugins.transcoder.image.enable").Default(true)
|
||||
func Init(conf *Config) {
|
||||
plugin_enable := conf.Get("transcoder.image.enable").Default(true).Bool()
|
||||
|
||||
cachePath := filepath.Join(GetCurrentDir(), ImageCachePath)
|
||||
os.RemoveAll(cachePath)
|
||||
os.MkdirAll(cachePath, os.ModePerm)
|
||||
|
||||
return []Plugin{
|
||||
{
|
||||
Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle
|
||||
Call: hook, // actual function we trigger
|
||||
Priority: -1, // last plugin to execute
|
||||
},
|
||||
}
|
||||
}
|
||||
Hooks.Register.ProcessFileContentBeforeSend(func (reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||
if plugin_enable == false {
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func hook(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){
|
||||
if ctx.Config.Get("plugins.transcoder.image.enable").Bool() == false {
|
||||
return reader, nil
|
||||
}
|
||||
Log.Debug("Plugin::Image")
|
||||
query := req.URL.Query()
|
||||
mType := GetMimeType(query.Get("path"))
|
||||
|
||||
query := req.URL.Query()
|
||||
mType := GetMimeType(query.Get("path"))
|
||||
if strings.HasPrefix(mType, "image/") == false {
|
||||
return reader, nil
|
||||
} else if mType == "image/svg" {
|
||||
return reader, nil
|
||||
} else if mType == "image/x-icon" {
|
||||
return reader, nil
|
||||
} else if query.Get("thumbnail") != "true" && query.Get("size") == "" {
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(mType, "image/") == false {
|
||||
return reader, nil
|
||||
} else if mType == "image/svg" {
|
||||
return reader, nil
|
||||
} else if mType == "image/x-icon" {
|
||||
return reader, nil
|
||||
} else if query.Get("thumbnail") != "true" && query.Get("size") == "" {
|
||||
return reader, nil
|
||||
}
|
||||
/////////////////////////
|
||||
// Specify transformation
|
||||
transform := &lib.Transform{
|
||||
Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)),
|
||||
Size: 300,
|
||||
Crop: true,
|
||||
Quality: 50,
|
||||
Exif: false,
|
||||
}
|
||||
if query.Get("thumbnail") == "true" {
|
||||
(*res).Header().Set("Cache-Control", "max-age=259200")
|
||||
} else if query.Get("size") != "" {
|
||||
(*res).Header().Set("Cache-Control", "max-age=600")
|
||||
size, err := strconv.ParseInt(query.Get("size"), 10, 64)
|
||||
if err != nil {
|
||||
return reader, nil
|
||||
}
|
||||
transform.Size = int(size)
|
||||
transform.Crop = false
|
||||
transform.Quality = 90
|
||||
transform.Exif = true
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Specify transformation
|
||||
transform := &lib.Transform{
|
||||
Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)),
|
||||
Size: 300,
|
||||
Crop: true,
|
||||
Quality: 50,
|
||||
Exif: false,
|
||||
}
|
||||
if query.Get("thumbnail") == "true" {
|
||||
(*res).Header().Set("Cache-Control", "max-age=259200")
|
||||
} else if query.Get("size") != "" {
|
||||
(*res).Header().Set("Cache-Control", "max-age=600")
|
||||
size, err := strconv.ParseInt(query.Get("size"), 10, 64)
|
||||
/////////////////////////////
|
||||
// Insert file in the fs
|
||||
// => lower RAM usage while processing
|
||||
file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return reader, NewError("Can't use filesystem", 500)
|
||||
}
|
||||
io.Copy(file, reader)
|
||||
file.Close()
|
||||
if obj, ok := reader.(interface{ Close() error }); ok {
|
||||
obj.Close()
|
||||
}
|
||||
defer func() {
|
||||
os.Remove(transform.Temporary)
|
||||
}()
|
||||
|
||||
/////////////////////////
|
||||
// Transcode RAW image
|
||||
if lib.IsRaw(mType) {
|
||||
if lib.ExtractPreview(transform) == nil {
|
||||
mType = "image/jpeg"
|
||||
(*res).Header().Set("Content-Type", mType)
|
||||
} else {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Final stage: resizing
|
||||
if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" {
|
||||
return reader, nil
|
||||
}
|
||||
transform.Size = int(size)
|
||||
transform.Crop = false
|
||||
transform.Quality = 90
|
||||
transform.Exif = true
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// Insert file in the fs
|
||||
// => lower RAM usage while processing
|
||||
file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
return reader, NewError("Can't use filesystem", 500)
|
||||
}
|
||||
io.Copy(file, reader)
|
||||
file.Close()
|
||||
if obj, ok := reader.(interface{ Close() error }); ok {
|
||||
obj.Close()
|
||||
}
|
||||
defer func() {
|
||||
os.Remove(transform.Temporary)
|
||||
}()
|
||||
|
||||
/////////////////////////
|
||||
// Transcode RAW image
|
||||
if lib.IsRaw(mType) {
|
||||
if lib.ExtractPreview(transform) == nil {
|
||||
mType = "image/jpeg"
|
||||
(*res).Header().Set("Content-Type", mType)
|
||||
} else {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Final stage: resizing
|
||||
if mType != "image/jpeg" && mType != "image/png" && mType != "image/gif" && mType != "image/tiff" {
|
||||
return reader, nil
|
||||
}
|
||||
return lib.CreateThumbnail(transform)
|
||||
return lib.CreateThumbnail(transform)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,24 +1,17 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"os"
|
||||
"path/filepath"
|
||||
plg "plugin"
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"sort"
|
||||
"strings"
|
||||
"net/http"
|
||||
"io"
|
||||
)
|
||||
|
||||
const PluginPath = "data/plugin/"
|
||||
|
||||
var plugins = make(map[string][]Plugin)
|
||||
|
||||
func init() {
|
||||
ex, _ := os.Executable()
|
||||
pPath := filepath.Join(filepath.Dir(ex), PluginPath)
|
||||
|
||||
pPath := filepath.Join(filepath.Dir(ex), PLUGIN_PATH)
|
||||
|
||||
file, err := os.Open(pPath)
|
||||
if err != nil {
|
||||
return
|
||||
@ -28,7 +21,7 @@ func init() {
|
||||
c := NewConfig()
|
||||
for i:=0; i < len(files); i++ {
|
||||
name := files[i].Name()
|
||||
if strings.HasPrefix(name, ".") == true {
|
||||
if strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
p, err := plg.Open(pPath + "/" + name)
|
||||
@ -36,32 +29,13 @@ func init() {
|
||||
Log.Warning("Can't load plugin: %s => %v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
f, err := p.Lookup("Register")
|
||||
fn, err := p.Lookup("Init")
|
||||
if err != nil {
|
||||
Log.Warning("Can't register plugin: %s => %v", name, err)
|
||||
continue
|
||||
}
|
||||
if obj, ok := f.(func(config *Config) []Plugin); ok {
|
||||
for _, plg := range obj(c) {
|
||||
plugins[plg.Type] = append(plugins[plg.Type], plg)
|
||||
sort.SliceStable(plugins[plg.Type], func(i, j int) bool {
|
||||
return plugins[plg.Type][i].Priority > plugins[plg.Type][j].Priority
|
||||
})
|
||||
}
|
||||
if obj, ok := fn.(func(config *Config)); ok {
|
||||
obj(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func ProcessFileContentBeforeSend() []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) {
|
||||
fs := plugins[PROCESS_FILE_CONTENT_BEFORE_SEND]
|
||||
ret := make([]func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error), len(fs))
|
||||
for _, p := range fs {
|
||||
if f, ok := p.Call.(func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error)); ok {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user