mirror of
				https://github.com/mickael-kerjean/filestash.git
				synced 2025-11-01 02:43:35 +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() { | func (this Config) save() { | ||||||
|  | 	if this.path == nil { | ||||||
|  | 		Log.Error("Config error") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
| 	if gjson.Valid(this.json) == false { | 	if gjson.Valid(this.json) == false { | ||||||
|  | 		Log.Error("Config error") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil { | 	if f, err := os.OpenFile(configPath, os.O_WRONLY|os.O_CREATE, os.ModePerm); err == nil { | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package common | |||||||
| const ( | const ( | ||||||
| 	APP_VERSION = "v0.3" | 	APP_VERSION = "v0.3" | ||||||
| 	CONFIG_PATH = "data/config/" | 	CONFIG_PATH = "data/config/" | ||||||
|  | 	PLUGIN_PATH = "data/plugin/" | ||||||
| 	COOKIE_NAME_AUTH = "auth" | 	COOKIE_NAME_AUTH = "auth" | ||||||
| 	COOKIE_NAME_PROOF = "proof" | 	COOKIE_NAME_PROOF = "proof" | ||||||
| 	COOKIE_PATH = "/api/" | 	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 | package common | ||||||
|  |  | ||||||
| type Plugin struct { | import ( | ||||||
| 	Type     string | 	"io" | ||||||
| 	Call     interface{} | 	"net/http" | ||||||
| 	Priority int | ) | ||||||
|  |  | ||||||
|  | type Register struct{} | ||||||
|  | type Get struct{} | ||||||
|  |  | ||||||
|  | var Hooks = struct { | ||||||
|  | 	Get Get | ||||||
|  | 	Register Register | ||||||
|  | }{ | ||||||
|  | 	Get: Get{}, | ||||||
|  | 	Register: Register{}, | ||||||
| } | } | ||||||
|  |  | ||||||
| const ( | var process_file_content_before_send []func(io.Reader, *App, *http.ResponseWriter, *http.Request) (io.Reader, error) | ||||||
| 	PROCESS_FILE_CONTENT_BEFORE_SEND = "PROCESS_FILE_CONTENT_BEFORE_SEND" | 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 ( | import ( | ||||||
| 	. "github.com/mickael-kerjean/nuage/server/common" | 	. "github.com/mickael-kerjean/nuage/server/common" | ||||||
| 	"github.com/mickael-kerjean/nuage/server/model" | 	"github.com/mickael-kerjean/nuage/server/model" | ||||||
| 	"github.com/mickael-kerjean/nuage/server/plugin" |  | ||||||
| 	"io" | 	"io" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| @ -114,10 +113,7 @@ func FileCat(ctx App, res http.ResponseWriter, req *http.Request) { | |||||||
| 	mType := GetMimeType(req.URL.Query().Get("path")) | 	mType := GetMimeType(req.URL.Query().Get("path")) | ||||||
| 	res.Header().Set("Content-Type", mType) | 	res.Header().Set("Content-Type", mType) | ||||||
|  |  | ||||||
| 	for _, obj := range plugin.ProcessFileContentBeforeSend() { | 	for _, obj := range Hooks.Get.ProcessFileContentBeforeSend() { | ||||||
| 		if obj == nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if file, err = obj(file, &ctx, &res, req); err != nil { | 		if file, err = obj(file, &ctx, &res, req); err != nil { | ||||||
| 			SendErrorResult(res, err) | 			SendErrorResult(res, err) | ||||||
| 			return | 			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/" | 	ImageCachePath = "data/cache/image/" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Register(conf *Config) []Plugin { | func Init(conf *Config) { | ||||||
| 	conf.Get("plugins.transcoder.image.enable").Default(true) | 	plugin_enable := conf.Get("transcoder.image.enable").Default(true).Bool() | ||||||
|  |  | ||||||
| 	cachePath := filepath.Join(GetCurrentDir(), ImageCachePath) | 	cachePath := filepath.Join(GetCurrentDir(), ImageCachePath) | ||||||
| 	os.RemoveAll(cachePath) | 	os.RemoveAll(cachePath) | ||||||
| 	os.MkdirAll(cachePath, os.ModePerm) | 	os.MkdirAll(cachePath, os.ModePerm) | ||||||
|  |  | ||||||
| 	return []Plugin{ | 	Hooks.Register.ProcessFileContentBeforeSend(func (reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ | ||||||
| 		{ | 		if plugin_enable == false { | ||||||
| 			Type: PROCESS_FILE_CONTENT_BEFORE_SEND, // where to hook our plugin in the request lifecycle | 			return reader, nil | ||||||
| 			Call: hook, // actual function we trigger | 		} | ||||||
| 			Priority: -1, // last plugin to execute |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func hook(reader io.Reader, ctx *App, res *http.ResponseWriter, req *http.Request) (io.Reader, error){ | 		query := req.URL.Query() | ||||||
| 	if ctx.Config.Get("plugins.transcoder.image.enable").Bool() == false { | 		mType := GetMimeType(query.Get("path")) | ||||||
| 		return reader, nil |  | ||||||
| 	} |  | ||||||
| 	Log.Debug("Plugin::Image") |  | ||||||
|  |  | ||||||
| 	query := req.URL.Query() | 		if strings.HasPrefix(mType, "image/") == false { | ||||||
| 	mType := GetMimeType(query.Get("path")) | 			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 | 		// Specify transformation | ||||||
| 	} else if mType == "image/svg" { | 		transform := &lib.Transform{ | ||||||
| 		return reader, nil | 			Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)), | ||||||
| 	} else if mType == "image/x-icon" { | 			Size:      300, | ||||||
| 		return reader, nil | 			Crop:      true, | ||||||
| 	} else if query.Get("thumbnail") != "true" && query.Get("size") == "" { | 			Quality:   50, | ||||||
| 		return reader, nil | 			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 | 		// Insert file in the fs | ||||||
| 	transform := &lib.Transform{ | 		// => lower RAM usage while processing | ||||||
| 		Temporary: GetAbsolutePath(ImageCachePath + "image_" + QuickString(10)), | 		file, err := os.OpenFile(transform.Temporary, os.O_WRONLY|os.O_CREATE, os.ModePerm) | ||||||
| 		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 { | 		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 reader, nil | ||||||
| 		} | 		} | ||||||
| 		transform.Size = int(size) | 		return lib.CreateThumbnail(transform) | ||||||
| 		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) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,24 +1,17 @@ | |||||||
| package plugin | package plugin | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	. "github.com/mickael-kerjean/nuage/server/common" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	plg "plugin" | 	plg "plugin" | ||||||
| 	. "github.com/mickael-kerjean/nuage/server/common" |  | ||||||
| 	"sort" |  | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"net/http" |  | ||||||
| 	"io" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const PluginPath = "data/plugin/" |  | ||||||
|  |  | ||||||
| var plugins = make(map[string][]Plugin) |  | ||||||
|  |  | ||||||
| func init() { | func init() { | ||||||
| 	ex, _ := os.Executable() | 	ex, _ := os.Executable() | ||||||
| 	pPath := filepath.Join(filepath.Dir(ex), PluginPath) | 	pPath := filepath.Join(filepath.Dir(ex), PLUGIN_PATH) | ||||||
| 	 |  | ||||||
| 	file, err := os.Open(pPath) | 	file, err := os.Open(pPath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return | 		return | ||||||
| @ -28,7 +21,7 @@ func init() { | |||||||
| 	c := NewConfig() | 	c := NewConfig() | ||||||
| 	for i:=0; i < len(files); i++ { | 	for i:=0; i < len(files); i++ { | ||||||
| 		name := files[i].Name() | 		name := files[i].Name() | ||||||
| 		if strings.HasPrefix(name, ".") == true { | 		if strings.HasPrefix(name, ".") { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		p, err := plg.Open(pPath + "/" + name) | 		p, err := plg.Open(pPath + "/" + name) | ||||||
| @ -36,32 +29,13 @@ func init() { | |||||||
| 			Log.Warning("Can't load plugin: %s => %v", name, err) | 			Log.Warning("Can't load plugin: %s => %v", name, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		 | 		fn, err := p.Lookup("Init") | ||||||
| 		f, err := p.Lookup("Register") |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			Log.Warning("Can't register plugin: %s => %v", name, err) | 			Log.Warning("Can't register plugin: %s => %v", name, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if obj, ok := f.(func(config *Config) []Plugin); ok { | 		if obj, ok := fn.(func(config *Config)); ok { | ||||||
| 			for _, plg := range obj(c) { | 			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 |  | ||||||
| 				}) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| 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
	 Mickael KERJEAN
					Mickael KERJEAN