mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Add plugin tasks (#651)
This commit is contained in:
3
pkg/plugin/common/doc.go
Normal file
3
pkg/plugin/common/doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package common encapulates data structures and functions that will be used
|
||||
// by plugin executables and the plugin subsystem in the stash server.
|
||||
package common
|
||||
203
pkg/plugin/common/log/log.go
Normal file
203
pkg/plugin/common/log/log.go
Normal file
@@ -0,0 +1,203 @@
|
||||
// Package log provides a number of logging utility functions for encoding and
|
||||
// decoding log messages between a stash server and a plugin instance.
|
||||
//
|
||||
// Log messages sent from a plugin instance are transmitted via stderr and are
|
||||
// encoded with a prefix consisting of special character SOH, then the log
|
||||
// level (one of t, d, i, w, e, or p - corresponding to trace, debug, info,
|
||||
// warning, error and progress levels respectively), then special character
|
||||
// STX.
|
||||
//
|
||||
// The Trace, Debug, Info, Warning, and Error methods, and their equivalent
|
||||
// formatted methods are intended for use by plugin instances to transmit log
|
||||
// messages. The Progress method is also intended for sending progress data.
|
||||
//
|
||||
// Conversely, LevelFromName and DetectLogLevel are intended for use by the
|
||||
// stash server.
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Level represents a logging level for plugin outputs.
|
||||
type Level struct {
|
||||
char byte
|
||||
Name string
|
||||
}
|
||||
|
||||
// Valid Level values.
|
||||
var (
|
||||
TraceLevel = Level{
|
||||
char: 't',
|
||||
Name: "trace",
|
||||
}
|
||||
DebugLevel = Level{
|
||||
char: 'd',
|
||||
Name: "debug",
|
||||
}
|
||||
InfoLevel = Level{
|
||||
char: 'i',
|
||||
Name: "info",
|
||||
}
|
||||
WarningLevel = Level{
|
||||
char: 'w',
|
||||
Name: "warning",
|
||||
}
|
||||
ErrorLevel = Level{
|
||||
char: 'e',
|
||||
Name: "error",
|
||||
}
|
||||
ProgressLevel = Level{
|
||||
char: 'p',
|
||||
}
|
||||
NoneLevel = Level{
|
||||
Name: "none",
|
||||
}
|
||||
)
|
||||
|
||||
var validLevels = []Level{
|
||||
TraceLevel,
|
||||
DebugLevel,
|
||||
InfoLevel,
|
||||
WarningLevel,
|
||||
ErrorLevel,
|
||||
ProgressLevel,
|
||||
NoneLevel,
|
||||
}
|
||||
|
||||
const startLevelChar byte = 1
|
||||
const endLevelChar byte = 2
|
||||
|
||||
func (l Level) prefix() string {
|
||||
return string([]byte{
|
||||
startLevelChar,
|
||||
byte(l.char),
|
||||
endLevelChar,
|
||||
})
|
||||
}
|
||||
|
||||
func (l Level) log(args ...interface{}) {
|
||||
if l.char == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
argsToUse := []interface{}{
|
||||
l.prefix(),
|
||||
}
|
||||
argsToUse = append(argsToUse, args...)
|
||||
fmt.Fprintln(os.Stderr, argsToUse...)
|
||||
}
|
||||
|
||||
func (l Level) logf(format string, args ...interface{}) {
|
||||
if l.char == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
formatToUse := string(l.prefix()) + format + "\n"
|
||||
fmt.Fprintf(os.Stderr, formatToUse, args...)
|
||||
}
|
||||
|
||||
// Trace outputs a trace logging message to os.Stderr. Message is encoded with a
|
||||
// prefix that signifies to the server that it is a trace message.
|
||||
func Trace(args ...interface{}) {
|
||||
TraceLevel.log(args...)
|
||||
}
|
||||
|
||||
// Tracef is the equivalent of Printf outputting as a trace logging message.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
TraceLevel.logf(format, args...)
|
||||
}
|
||||
|
||||
// Debug outputs a debug logging message to os.Stderr. Message is encoded with a
|
||||
// prefix that signifies to the server that it is a debug message.
|
||||
func Debug(args ...interface{}) {
|
||||
DebugLevel.log(args...)
|
||||
}
|
||||
|
||||
// Debugf is the equivalent of Printf outputting as a debug logging message.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
DebugLevel.logf(format, args...)
|
||||
}
|
||||
|
||||
// Info outputs an info logging message to os.Stderr. Message is encoded with a
|
||||
// prefix that signifies to the server that it is an info message.
|
||||
func Info(args ...interface{}) {
|
||||
InfoLevel.log(args...)
|
||||
}
|
||||
|
||||
// Infof is the equivalent of Printf outputting as an info logging message.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
InfoLevel.logf(format, args...)
|
||||
}
|
||||
|
||||
// Warn outputs a warning logging message to os.Stderr. Message is encoded with a
|
||||
// prefix that signifies to the server that it is a warning message.
|
||||
func Warn(args ...interface{}) {
|
||||
WarningLevel.log(args...)
|
||||
}
|
||||
|
||||
// Warnf is the equivalent of Printf outputting as a warning logging message.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
WarningLevel.logf(format, args...)
|
||||
}
|
||||
|
||||
// Error outputs an error logging message to os.Stderr. Message is encoded with a
|
||||
// prefix that signifies to the server that it is an error message.
|
||||
func Error(args ...interface{}) {
|
||||
ErrorLevel.log(args...)
|
||||
}
|
||||
|
||||
// Errorf is the equivalent of Printf outputting as an error logging message.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
ErrorLevel.logf(format, args...)
|
||||
}
|
||||
|
||||
// Progress logs the current progress value. The progress value should be
|
||||
// between 0 and 1.0 inclusively, with 1 representing that the task is
|
||||
// complete. Values outside of this range will be clamp to be within it.
|
||||
func Progress(progress float64) {
|
||||
progress = math.Min(math.Max(0, progress), 1)
|
||||
ProgressLevel.log(progress)
|
||||
}
|
||||
|
||||
// LevelFromName returns the Level that matches the provided name or nil if
|
||||
// the name does not match a valid value.
|
||||
func LevelFromName(name string) *Level {
|
||||
for _, l := range validLevels {
|
||||
if l.Name == name {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectLogLevel returns the Level and the logging string for a provided line
|
||||
// of plugin output. It parses the string for logging control characters and
|
||||
// determines the log level, if present. If not present, the plugin output
|
||||
// is returned unchanged with a nil Level.
|
||||
func DetectLogLevel(line string) (*Level, string) {
|
||||
if len(line) < 4 || line[0] != startLevelChar || line[2] != endLevelChar {
|
||||
return nil, line
|
||||
}
|
||||
|
||||
char := line[1]
|
||||
var level *Level
|
||||
for _, l := range validLevels {
|
||||
if l.char == char {
|
||||
level = &l
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if level == nil {
|
||||
return nil, line
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line[3:])
|
||||
|
||||
return level, line
|
||||
}
|
||||
99
pkg/plugin/common/msg.go
Normal file
99
pkg/plugin/common/msg.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package common
|
||||
|
||||
import "net/http"
|
||||
|
||||
// StashServerConnection represents the connection details needed for a
|
||||
// plugin instance to connect to its parent stash server.
|
||||
type StashServerConnection struct {
|
||||
// http or https
|
||||
Scheme string
|
||||
|
||||
Port int
|
||||
|
||||
// Cookie for authentication purposes
|
||||
SessionCookie *http.Cookie
|
||||
|
||||
// Dir specifies the directory containing the stash server's configuration
|
||||
// file.
|
||||
Dir string
|
||||
|
||||
// PluginDir specifies the directory containing the plugin configuration
|
||||
// file.
|
||||
PluginDir string
|
||||
}
|
||||
|
||||
// PluginArgValue represents a single value parameter for plugin operations.
|
||||
type PluginArgValue interface{}
|
||||
|
||||
// ArgsMap is a map of argument key to value.
|
||||
type ArgsMap map[string]PluginArgValue
|
||||
|
||||
// String returns the string field or an empty string if the string field is
|
||||
// nil
|
||||
func (m ArgsMap) String(key string) string {
|
||||
v, found := m[key]
|
||||
var ret string
|
||||
if !found {
|
||||
return ret
|
||||
}
|
||||
ret, _ = v.(string)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Int returns the int field or 0 if the int field is nil
|
||||
func (m ArgsMap) Int(key string) int {
|
||||
v, found := m[key]
|
||||
var ret int
|
||||
if !found {
|
||||
return ret
|
||||
}
|
||||
ret, _ = v.(int)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Bool returns the boolean field or false if the boolean field is nil
|
||||
func (m ArgsMap) Bool(key string) bool {
|
||||
v, found := m[key]
|
||||
var ret bool
|
||||
if !found {
|
||||
return ret
|
||||
}
|
||||
ret, _ = v.(bool)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Float returns the float field or 0 if the float field is nil
|
||||
func (m ArgsMap) Float(key string) float64 {
|
||||
v, found := m[key]
|
||||
var ret float64
|
||||
if !found {
|
||||
return ret
|
||||
}
|
||||
ret, _ = v.(float64)
|
||||
return ret
|
||||
}
|
||||
|
||||
// PluginInput is the data structure that is sent to plugin instances when they
|
||||
// are spawned.
|
||||
type PluginInput struct {
|
||||
// Server details to connect to the stash server.
|
||||
ServerConnection StashServerConnection `json:"server_connection"`
|
||||
|
||||
// Arguments to the plugin operation.
|
||||
Args ArgsMap `json:"args"`
|
||||
}
|
||||
|
||||
// PluginOutput is the data structure that is expected to be output by plugin
|
||||
// processes when execution has concluded. It is expected that this data will
|
||||
// be encoded as JSON.
|
||||
type PluginOutput struct {
|
||||
Error *string `json:"error"`
|
||||
Output interface{} `json:"output"`
|
||||
}
|
||||
|
||||
// SetError is a convenience method that sets the Error field based on the
|
||||
// provided error.
|
||||
func (o *PluginOutput) SetError(err error) {
|
||||
errStr := err.Error()
|
||||
o.Error = &errStr
|
||||
}
|
||||
30
pkg/plugin/common/rpc.go
Normal file
30
pkg/plugin/common/rpc.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"net/rpc/jsonrpc"
|
||||
|
||||
"github.com/natefinch/pie"
|
||||
)
|
||||
|
||||
// RPCRunner is the interface that RPC plugins are expected to fulfil.
|
||||
type RPCRunner interface {
|
||||
// Perform the operation, using the provided input and populating the
|
||||
// output object.
|
||||
Run(input PluginInput, output *PluginOutput) error
|
||||
|
||||
// Stop any running operations, if possible. No input is sent and any
|
||||
// output is ignored.
|
||||
Stop(input struct{}, output *bool) error
|
||||
}
|
||||
|
||||
// ServePlugin is used by plugin instances to serve the plugin via RPC, using
|
||||
// the provided RPCRunner interface.
|
||||
func ServePlugin(iface RPCRunner) error {
|
||||
p := pie.NewProvider()
|
||||
if err := p.RegisterName("RPCRunner", iface); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.ServeCodec(jsonrpc.NewServerCodec)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user