mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 13:25:21 +01:00
232 lines
5.1 KiB
Go
232 lines
5.1 KiB
Go
package plugins
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gobuffalo/buffalo-plugins/plugins/plugdeps"
|
|
"github.com/gobuffalo/envy"
|
|
"github.com/gobuffalo/meta"
|
|
"github.com/karrick/godirwalk"
|
|
"github.com/markbates/oncer"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const timeoutEnv = "BUFFALO_PLUGIN_TIMEOUT"
|
|
|
|
var t = time.Second * 2
|
|
|
|
func timeout() time.Duration {
|
|
oncer.Do("plugins.timeout", func() {
|
|
rawTimeout, err := envy.MustGet(timeoutEnv)
|
|
if err == nil {
|
|
if parsed, err := time.ParseDuration(rawTimeout); err == nil {
|
|
t = parsed
|
|
} else {
|
|
logrus.Errorf("%q value is malformed assuming default %q: %v", timeoutEnv, t, err)
|
|
}
|
|
} else {
|
|
logrus.Debugf("%q not set, assuming default of %v", timeoutEnv, t)
|
|
}
|
|
})
|
|
return t
|
|
}
|
|
|
|
// List maps a Buffalo command to a slice of Command
|
|
type List map[string]Commands
|
|
|
|
var _list List
|
|
|
|
// Available plugins for the `buffalo` command.
|
|
// It will look in $GOPATH/bin and the `./plugins` directory.
|
|
// This can be changed by setting the $BUFFALO_PLUGIN_PATH
|
|
// environment variable.
|
|
//
|
|
// Requirements:
|
|
// * file/command must be executable
|
|
// * file/command must start with `buffalo-`
|
|
// * file/command must respond to `available` and return JSON of
|
|
// plugins.Commands{}
|
|
//
|
|
// Limit full path scan with direct plugin path
|
|
//
|
|
// If a file/command doesn't respond to being invoked with `available`
|
|
// within one second, buffalo will assume that it is unable to load. This
|
|
// can be changed by setting the $BUFFALO_PLUGIN_TIMEOUT environment
|
|
// variable. It must be set to a duration that `time.ParseDuration` can
|
|
// process.
|
|
func Available() (List, error) {
|
|
var err error
|
|
oncer.Do("plugins.Available", func() {
|
|
defer func() {
|
|
if err := saveCache(); err != nil {
|
|
logrus.Error(err)
|
|
}
|
|
}()
|
|
|
|
app := meta.New(".")
|
|
|
|
if plugdeps.On(app) {
|
|
_list, err = listPlugDeps(app)
|
|
return
|
|
}
|
|
|
|
paths := []string{"plugins"}
|
|
|
|
from, err := envy.MustGet("BUFFALO_PLUGIN_PATH")
|
|
if err != nil {
|
|
from, err = envy.MustGet("GOPATH")
|
|
if err != nil {
|
|
return
|
|
}
|
|
from = filepath.Join(from, "bin")
|
|
}
|
|
|
|
paths = append(paths, strings.Split(from, string(os.PathListSeparator))...)
|
|
|
|
list := List{}
|
|
for _, p := range paths {
|
|
if ignorePath(p) {
|
|
continue
|
|
}
|
|
if _, err := os.Stat(p); err != nil {
|
|
continue
|
|
}
|
|
|
|
err := godirwalk.Walk(p, &godirwalk.Options{
|
|
FollowSymbolicLinks: true,
|
|
Callback: func(path string, info *godirwalk.Dirent) error {
|
|
if err != nil {
|
|
// May indicate a permissions problem with the path, skip it
|
|
return nil
|
|
}
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
base := filepath.Base(path)
|
|
if strings.HasPrefix(base, "buffalo-") {
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout())
|
|
commands := askBin(ctx, path)
|
|
cancel()
|
|
for _, c := range commands {
|
|
bc := c.BuffaloCommand
|
|
if _, ok := list[bc]; !ok {
|
|
list[bc] = Commands{}
|
|
}
|
|
c.Binary = path
|
|
list[bc] = append(list[bc], c)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
_list = list
|
|
})
|
|
return _list, err
|
|
}
|
|
|
|
func askBin(ctx context.Context, path string) Commands {
|
|
start := time.Now()
|
|
defer func() {
|
|
logrus.Debugf("askBin %s=%.4f s", path, time.Since(start).Seconds())
|
|
}()
|
|
|
|
commands := Commands{}
|
|
defer func() {
|
|
addToCache(path, cachedPlugin{
|
|
Commands: commands,
|
|
})
|
|
}()
|
|
if cp, ok := findInCache(path); ok {
|
|
s := sum(path)
|
|
if s == cp.CheckSum {
|
|
logrus.Debugf("cache hit: %s", path)
|
|
commands = cp.Commands
|
|
return commands
|
|
}
|
|
}
|
|
logrus.Debugf("cache miss: %s", path)
|
|
if strings.HasPrefix(filepath.Base(path), "buffalo-no-sqlite") {
|
|
return commands
|
|
}
|
|
|
|
cmd := exec.CommandContext(ctx, path, "available")
|
|
bb := &bytes.Buffer{}
|
|
cmd.Stdout = bb
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return commands
|
|
}
|
|
msg := bb.String()
|
|
for len(msg) > 0 {
|
|
err = json.NewDecoder(strings.NewReader(msg)).Decode(&commands)
|
|
if err == nil {
|
|
return commands
|
|
}
|
|
msg = msg[1:]
|
|
}
|
|
logrus.Errorf("[PLUGIN] error decoding plugin %s: %s\n%s\n", path, err, msg)
|
|
return commands
|
|
}
|
|
|
|
func ignorePath(p string) bool {
|
|
p = strings.ToLower(p)
|
|
for _, x := range []string{`c:\windows`, `c:\program`} {
|
|
if strings.HasPrefix(p, x) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func listPlugDeps(app meta.App) (List, error) {
|
|
list := List{}
|
|
plugs, err := plugdeps.List(app)
|
|
if err != nil {
|
|
return list, err
|
|
}
|
|
for _, p := range plugs.List() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout())
|
|
defer cancel()
|
|
bin := p.Binary
|
|
if len(p.Local) != 0 {
|
|
bin = p.Local
|
|
}
|
|
bin, err := LookPath(bin)
|
|
if err != nil {
|
|
if errors.Cause(err) != ErrPlugMissing {
|
|
return list, err
|
|
}
|
|
continue
|
|
}
|
|
commands := askBin(ctx, bin)
|
|
cancel()
|
|
for _, c := range commands {
|
|
bc := c.BuffaloCommand
|
|
if _, ok := list[bc]; !ok {
|
|
list[bc] = Commands{}
|
|
}
|
|
c.Binary = p.Binary
|
|
for _, pc := range p.Commands {
|
|
if c.Name == pc.Name {
|
|
c.Flags = pc.Flags
|
|
break
|
|
}
|
|
}
|
|
list[bc] = append(list[bc], c)
|
|
}
|
|
}
|
|
return list, nil
|
|
}
|