Преглед изворни кода

feat: remap positive plugin assets

Yeuoly пре 10 месеци
родитељ
комит
de788d49d0

+ 25 - 2
internal/core/plugin_manager/watcher.go

@@ -55,8 +55,15 @@ func (p *PluginManager) handleNewPlugins(config *app.Config) {
 	for plugin := range p.loadNewPlugins(config.PluginStoragePath) {
 		var plugin_interface plugin_entities.PluginRuntimeInterface
 
+		// get assets
+		assets, err := plugin.Decoder.Assets()
+		if err != nil {
+			log.Error("get plugin assets error: %v", err)
+			continue
+		}
+
 		if config.Platform == app.PLATFORM_AWS_LAMBDA {
-			plugin_interface = &aws_manager.AWSPluginRuntime{
+			aws_plugin_runtime := &aws_manager.AWSPluginRuntime{
 				PluginRuntime: plugin.Runtime,
 				PositivePluginRuntime: positive_manager.PositivePluginRuntime{
 					BasicPluginRuntime: basic_manager.NewBasicPluginRuntime(p.mediaManager),
@@ -65,8 +72,16 @@ func (p *PluginManager) handleNewPlugins(config *app.Config) {
 					Decoder:            plugin.Decoder,
 				},
 			}
+			if err := aws_plugin_runtime.RemapAssets(
+				&aws_plugin_runtime.Config,
+				assets,
+			); err != nil {
+				log.Error("remap plugin assets error: %v", err)
+				continue
+			}
+			plugin_interface = aws_plugin_runtime
 		} else if config.Platform == app.PLATFORM_LOCAL {
-			plugin_interface = &local_manager.LocalPluginRuntime{
+			local_plugin_runtime := &local_manager.LocalPluginRuntime{
 				PluginRuntime: plugin.Runtime,
 				PositivePluginRuntime: positive_manager.PositivePluginRuntime{
 					BasicPluginRuntime: basic_manager.NewBasicPluginRuntime(p.mediaManager),
@@ -75,6 +90,14 @@ func (p *PluginManager) handleNewPlugins(config *app.Config) {
 					Decoder:            plugin.Decoder,
 				},
 			}
+			if err := local_plugin_runtime.RemapAssets(
+				&local_plugin_runtime.Config,
+				assets,
+			); err != nil {
+				log.Error("remap plugin assets error: %v", err)
+				continue
+			}
+			plugin_interface = local_plugin_runtime
 		} else {
 			log.Error("unsupported platform: %s for plugin: %s", config.Platform, plugin.Runtime.Config.Name)
 			continue

+ 1 - 0
internal/core/plugin_packager/_assets/test.svg

@@ -0,0 +1 @@
+aaa

+ 32 - 0
internal/core/plugin_packager/decoder/decoder.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
+	"strings"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
@@ -22,6 +23,14 @@ type PluginDecoder interface {
 	// ReadFile reads the entire contents of a file and returns it as a byte slice
 	ReadFile(filename string) ([]byte, error)
 
+	// ReadDir reads the contents of a directory and returns a slice of strings
+	// The strings are the filenames, it's a full path and directory will not be included
+	// It executes recursively
+	// Example:
+	// - dirname: "config"
+	// - return: ["config/settings.yaml", "config/default.yaml"]
+	ReadDir(dirname string) ([]string, error)
+
 	// Close releases any resources used by the decoder
 	Close() error
 
@@ -40,6 +49,9 @@ type PluginDecoder interface {
 
 	// Manifest returns the manifest of the plugin
 	Manifest() (plugin_entities.PluginDeclaration, error)
+
+	// Assets returns a map of assets, the key is the filename, the value is the content
+	Assets() (map[string][]byte, error)
 }
 
 type PluginDecoderHelper struct {
@@ -104,3 +116,23 @@ func (p *PluginDecoderHelper) Manifest(decoder PluginDecoder) (plugin_entities.P
 	p.pluginDeclaration = &dec
 	return dec, nil
 }
+
+func (p *PluginDecoderHelper) Assets(decoder PluginDecoder) (map[string][]byte, error) {
+	files, err := decoder.ReadDir("_assets")
+	if err != nil {
+		return nil, err
+	}
+
+	assets := make(map[string][]byte)
+	for _, file := range files {
+		content, err := decoder.ReadFile(file)
+		if err != nil {
+			return nil, err
+		}
+		// trim _assets
+		file, _ = strings.CutPrefix(file, "_assets/")
+		assets[file] = content
+	}
+
+	return assets, nil
+}

+ 28 - 0
internal/core/plugin_packager/decoder/fs.go

@@ -94,6 +94,30 @@ func (d *FSPluginDecoder) ReadFile(filename string) ([]byte, error) {
 	return os.ReadFile(filepath.Join(d.root, filename))
 }
 
+func (d *FSPluginDecoder) ReadDir(dirname string) ([]string, error) {
+	var files []string
+	err := filepath.WalkDir(
+		filepath.Join(d.root, dirname),
+		func(path string, info fs.DirEntry, err error) error {
+			if err != nil {
+				return err
+			}
+			if !info.IsDir() {
+				rel_path, err := filepath.Rel(d.root, path)
+				if err != nil {
+					return err
+				}
+				files = append(files, rel_path)
+			}
+			return nil
+		},
+	)
+	if err != nil {
+		return nil, err
+	}
+	return files, nil
+}
+
 func (d *FSPluginDecoder) FileReader(filename string) (io.ReadCloser, error) {
 	return os.Open(filepath.Join(d.root, filename))
 }
@@ -109,3 +133,7 @@ func (d *FSPluginDecoder) CreateTime() (int64, error) {
 func (d *FSPluginDecoder) Manifest() (plugin_entities.PluginDeclaration, error) {
 	return d.PluginDecoderHelper.Manifest(d)
 }
+
+func (d *FSPluginDecoder) Assets() (map[string][]byte, error) {
+	return d.PluginDecoderHelper.Assets(d)
+}

+ 22 - 0
internal/core/plugin_packager/decoder/zip.go

@@ -6,6 +6,7 @@ import (
 	"io"
 	"io/fs"
 	"path"
+	"strings"
 
 	"github.com/langgenius/dify-plugin-daemon/internal/types/entities/plugin_entities"
 	"github.com/langgenius/dify-plugin-daemon/internal/utils/parser"
@@ -100,6 +101,23 @@ func (z *ZipPluginDecoder) ReadFile(filename string) ([]byte, error) {
 	return data.Bytes(), nil
 }
 
+func (z *ZipPluginDecoder) ReadDir(dirname string) ([]string, error) {
+	if z.reader == nil {
+		return nil, z.err
+	}
+
+	files := make([]string, 0)
+	dir_name_with_slash := strings.TrimSuffix(dirname, "/") + "/"
+
+	for _, file := range z.reader.File {
+		if strings.HasPrefix(file.Name, dir_name_with_slash) {
+			files = append(files, file.Name)
+		}
+	}
+
+	return files, nil
+}
+
 func (z *ZipPluginDecoder) FileReader(filename string) (io.ReadCloser, error) {
 	return z.reader.Open(filename)
 }
@@ -165,3 +183,7 @@ func (z *ZipPluginDecoder) CreateTime() (int64, error) {
 func (z *ZipPluginDecoder) Manifest() (plugin_entities.PluginDeclaration, error) {
 	return z.PluginDecoderHelper.Manifest(z)
 }
+
+func (z *ZipPluginDecoder) Assets() (map[string][]byte, error) {
+	return z.PluginDecoderHelper.Assets(z)
+}

+ 1 - 0
internal/core/plugin_packager/manifest.yaml

@@ -2,6 +2,7 @@ version: 0.0.1
 type: plugin
 author: "Yeuoly"
 name: "Neko"
+icon: test.svg
 label:
   en_US: "Neko"
 created_at: "2024-07-12T08:03:44.658609186Z"

+ 37 - 0
internal/core/plugin_packager/packager_test.go

@@ -17,6 +17,9 @@ var manifest []byte
 //go:embed neko.yaml
 var neko []byte
 
+//go:embed _assets/test.svg
+var test_svg []byte
+
 func TestPackagerAndVerifier(t *testing.T) {
 	// create a temp directory
 	os.RemoveAll("temp")
@@ -40,12 +43,34 @@ func TestPackagerAndVerifier(t *testing.T) {
 		return
 	}
 
+	if err := os.MkdirAll("temp/_assets", 0755); err != nil {
+		t.Errorf("failed to create _assets directory: %s", err.Error())
+		return
+	}
+
+	if err := os.WriteFile("temp/_assets/test.svg", test_svg, 0644); err != nil {
+		t.Errorf("failed to write test.svg: %s", err.Error())
+		return
+	}
+
 	origin_decoder, err := decoder.NewFSPluginDecoder("temp")
 	if err != nil {
 		t.Errorf("failed to create decoder: %s", err.Error())
 		return
 	}
 
+	// check assets
+	assets, err := origin_decoder.Assets()
+	if err != nil {
+		t.Errorf("failed to get assets: %s", err.Error())
+		return
+	}
+
+	if assets["test.svg"] == nil {
+		t.Errorf("should have test.svg asset, got %v", assets)
+		return
+	}
+
 	packager := packager.NewPackager(origin_decoder)
 
 	// pack
@@ -68,6 +93,18 @@ func TestPackagerAndVerifier(t *testing.T) {
 		return
 	}
 
+	// check assets
+	assets, err = signed_decoder.Assets()
+	if err != nil {
+		t.Errorf("failed to get assets: %s", err.Error())
+		return
+	}
+
+	if assets["test.svg"] == nil {
+		t.Errorf("should have test.svg asset, got %v", assets)
+		return
+	}
+
 	// verify
 	err = verifier.VerifyPlugin(signed_decoder)
 	if err != nil {