mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Plugin API improvements (#4603)
* Accept plain map for runPluginTask * Support running plugin task without task name * Add interface to run plugin operations * Update RunPluginTask client mutation
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
package plugin
|
||||
|
||||
type OperationInput map[string]interface{}
|
||||
|
||||
type PluginArgInput struct {
|
||||
Key string `json:"key"`
|
||||
Value *PluginValueInput `json:"value"`
|
||||
@@ -14,28 +16,11 @@ type PluginValueInput struct {
|
||||
A []*PluginValueInput `json:"a"`
|
||||
}
|
||||
|
||||
func findArg(args []*PluginArgInput, name string) *PluginArgInput {
|
||||
for _, v := range args {
|
||||
if v.Key == name {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyDefaultArgs(args []*PluginArgInput, defaultArgs map[string]string) []*PluginArgInput {
|
||||
func applyDefaultArgs(args OperationInput, defaultArgs map[string]string) {
|
||||
for k, v := range defaultArgs {
|
||||
if arg := findArg(args, k); arg == nil {
|
||||
v := v // Copy v, because it's being exported out of the loop
|
||||
args = append(args, &PluginArgInput{
|
||||
Key: k,
|
||||
Value: &PluginValueInput{
|
||||
Str: &v,
|
||||
},
|
||||
})
|
||||
_, found := args[k]
|
||||
if !found {
|
||||
args[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
@@ -295,7 +295,9 @@ func (c Config) getConfigPath() string {
|
||||
func (c Config) getExecCommand(task *OperationConfig) []string {
|
||||
ret := c.Exec
|
||||
|
||||
ret = append(ret, task.ExecArgs...)
|
||||
if task != nil {
|
||||
ret = append(ret, task.ExecArgs...)
|
||||
}
|
||||
|
||||
if len(ret) > 0 {
|
||||
_, err := exec.LookPath(ret[0])
|
||||
|
||||
@@ -4,38 +4,11 @@ import (
|
||||
"github.com/stashapp/stash/pkg/plugin/common"
|
||||
)
|
||||
|
||||
func toPluginArgs(args []*PluginArgInput) common.ArgsMap {
|
||||
func toPluginArgs(args OperationInput) common.ArgsMap {
|
||||
ret := make(common.ArgsMap)
|
||||
for _, a := range args {
|
||||
ret[a.Key] = toPluginArgValue(a.Value)
|
||||
for k, a := range args {
|
||||
ret[k] = common.PluginArgValue(a)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func toPluginArgValue(arg *PluginValueInput) common.PluginArgValue {
|
||||
if arg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case arg.Str != nil:
|
||||
return common.PluginArgValue(*arg.Str)
|
||||
case arg.I != nil:
|
||||
return common.PluginArgValue(*arg.I)
|
||||
case arg.B != nil:
|
||||
return common.PluginArgValue(*arg.B)
|
||||
case arg.F != nil:
|
||||
return common.PluginArgValue(*arg.F)
|
||||
case arg.O != nil:
|
||||
return common.PluginArgValue(toPluginArgs(arg.O))
|
||||
case arg.A != nil:
|
||||
var ret []common.PluginArgValue
|
||||
for _, v := range arg.A {
|
||||
ret = append(ret, toPluginArgValue(v))
|
||||
}
|
||||
return common.PluginArgValue(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,26 +2,40 @@ var tagName = "Hawwwwt"
|
||||
|
||||
function main() {
|
||||
var modeArg = input.Args.mode;
|
||||
try {
|
||||
if (modeArg == "" || modeArg == "add") {
|
||||
addTag();
|
||||
} else if (modeArg == "remove") {
|
||||
removeTag();
|
||||
} else if (modeArg == "long") {
|
||||
doLongTask();
|
||||
} else if (modeArg == "indef") {
|
||||
doIndefiniteTask();
|
||||
} else if (modeArg == "hook") {
|
||||
doHookTask();
|
||||
if (modeArg !== undefined) {
|
||||
try {
|
||||
if (modeArg == "" || modeArg == "add") {
|
||||
addTag();
|
||||
} else if (modeArg == "remove") {
|
||||
removeTag();
|
||||
} else if (modeArg == "long") {
|
||||
doLongTask();
|
||||
} else if (modeArg == "indef") {
|
||||
doIndefiniteTask();
|
||||
} else if (modeArg == "hook") {
|
||||
doHookTask();
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
Error: err
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
return {
|
||||
Error: err
|
||||
Output: "ok"
|
||||
};
|
||||
}
|
||||
|
||||
if (input.Args.error) {
|
||||
return {
|
||||
Error: input.Args.error
|
||||
};
|
||||
}
|
||||
|
||||
// immediate mode
|
||||
// just return the args
|
||||
return {
|
||||
Output: "ok"
|
||||
Output: input.Args
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,9 @@ func (t *jsPluginTask) makeOutput(o otto.Value) {
|
||||
return
|
||||
}
|
||||
|
||||
t.result.Output, _ = asObj.Get("Output")
|
||||
output, _ := asObj.Get("Output")
|
||||
t.result.Output, _ = output.Export()
|
||||
|
||||
err, _ := asObj.Get("Error")
|
||||
if !err.IsUndefined() {
|
||||
errStr := err.String()
|
||||
|
||||
@@ -9,6 +9,7 @@ package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -225,8 +226,10 @@ func (c Cache) ListPluginTasks() []*PluginTask {
|
||||
return ret
|
||||
}
|
||||
|
||||
func buildPluginInput(plugin *Config, operation *OperationConfig, serverConnection common.StashServerConnection, args []*PluginArgInput) common.PluginInput {
|
||||
args = applyDefaultArgs(args, operation.DefaultArgs)
|
||||
func buildPluginInput(plugin *Config, operation *OperationConfig, serverConnection common.StashServerConnection, args OperationInput) common.PluginInput {
|
||||
if operation != nil {
|
||||
applyDefaultArgs(args, operation.DefaultArgs)
|
||||
}
|
||||
serverConnection.PluginDir = plugin.getConfigPath()
|
||||
return common.PluginInput{
|
||||
ServerConnection: serverConnection,
|
||||
@@ -255,7 +258,7 @@ func (c Cache) makeServerConnection(ctx context.Context) common.StashServerConne
|
||||
// CreateTask runs the plugin operation for the pluginID and operation
|
||||
// name provided. Returns an error if the plugin or the operation could not be
|
||||
// resolved.
|
||||
func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName string, args []*PluginArgInput, progress chan float64) (Task, error) {
|
||||
func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName *string, args OperationInput, progress chan float64) (Task, error) {
|
||||
serverConnection := c.makeServerConnection(ctx)
|
||||
|
||||
if c.pluginDisabled(pluginID) {
|
||||
@@ -269,9 +272,12 @@ func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName st
|
||||
return nil, fmt.Errorf("no plugin with ID %s", pluginID)
|
||||
}
|
||||
|
||||
operation := plugin.getTask(operationName)
|
||||
if operation == nil {
|
||||
return nil, fmt.Errorf("no task with name %s in plugin %s", operationName, plugin.getName())
|
||||
var operation *OperationConfig
|
||||
if operationName != nil {
|
||||
operation = plugin.getTask(*operationName)
|
||||
if operation == nil {
|
||||
return nil, fmt.Errorf("no task with name %s in plugin %s", *operationName, plugin.getName())
|
||||
}
|
||||
}
|
||||
|
||||
task := pluginTask{
|
||||
@@ -285,6 +291,68 @@ func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName st
|
||||
return task.createTask(), nil
|
||||
}
|
||||
|
||||
func (c Cache) RunPlugin(ctx context.Context, pluginID string, args OperationInput) (interface{}, error) {
|
||||
serverConnection := c.makeServerConnection(ctx)
|
||||
|
||||
if c.pluginDisabled(pluginID) {
|
||||
return nil, fmt.Errorf("plugin %s is disabled", pluginID)
|
||||
}
|
||||
|
||||
// find the plugin
|
||||
plugin := c.getPlugin(pluginID)
|
||||
|
||||
pluginInput := buildPluginInput(plugin, nil, serverConnection, args)
|
||||
|
||||
pt := pluginTask{
|
||||
plugin: plugin,
|
||||
input: pluginInput,
|
||||
gqlHandler: c.gqlHandler,
|
||||
serverConfig: c.config,
|
||||
}
|
||||
|
||||
task := pt.createTask()
|
||||
if err := task.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := waitForTask(ctx, task); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output := task.GetResult()
|
||||
if output == nil {
|
||||
logger.Debugf("%s: returned no result", pluginID)
|
||||
return nil, nil
|
||||
} else {
|
||||
if output.Error != nil {
|
||||
return nil, errors.New(*output.Error)
|
||||
}
|
||||
|
||||
return output.Output, nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitForTask(ctx context.Context, task Task) error {
|
||||
// handle cancel from context
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
task.Wait()
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if err := task.Stop(); err != nil {
|
||||
logger.Warnf("could not stop task: %v", err)
|
||||
}
|
||||
return fmt.Errorf("operation cancelled")
|
||||
case <-c:
|
||||
// task finished normally
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Cache) ExecutePostHooks(ctx context.Context, id int, hookType HookTriggerEnum, input interface{}, inputFields []string) {
|
||||
if err := c.executePostHooks(ctx, hookType, common.HookContext{
|
||||
ID: id,
|
||||
@@ -343,21 +411,8 @@ func (c Cache) executePostHooks(ctx context.Context, hookType HookTriggerEnum, h
|
||||
return err
|
||||
}
|
||||
|
||||
// handle cancel from context
|
||||
c := make(chan struct{})
|
||||
go func() {
|
||||
task.Wait()
|
||||
close(c)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if err := task.Stop(); err != nil {
|
||||
logger.Warnf("could not stop task: %v", err)
|
||||
}
|
||||
return fmt.Errorf("operation cancelled")
|
||||
case <-c:
|
||||
// task finished normally
|
||||
if err := waitForTask(ctx, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := task.GetResult()
|
||||
|
||||
@@ -41,7 +41,7 @@ func (t *rawPluginTask) Start() error {
|
||||
|
||||
command := t.plugin.getExecCommand(t.operation)
|
||||
if len(command) == 0 {
|
||||
return fmt.Errorf("empty exec value in operation %s", t.operation.Name)
|
||||
return fmt.Errorf("empty exec value")
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
@@ -53,7 +53,7 @@ func (t *rpcPluginTask) Start() error {
|
||||
|
||||
command := t.plugin.getExecCommand(t.operation)
|
||||
if len(command) == 0 {
|
||||
return fmt.Errorf("empty exec value in operation %s", t.operation.Name)
|
||||
return fmt.Errorf("empty exec value")
|
||||
}
|
||||
|
||||
pluginErrReader, pluginErrWriter := io.Pipe()
|
||||
|
||||
Reference in New Issue
Block a user