Add plugin tasks (#651)

This commit is contained in:
WithoutPants
2020-08-08 12:05:35 +10:00
committed by GitHub
parent 0874852fa8
commit 0ffefa6e16
47 changed files with 2855 additions and 17 deletions

View File

@@ -0,0 +1,9 @@
# Building
From the base stash source directory:
```
go build -tags=plugin_example -o plugin_goraw.exe ./pkg/plugin/examples/goraw/...
go build -tags=plugin_example -o plugin_gorpc.exe ./pkg/plugin/examples/gorpc/...
```
Place the resulting binaries together with the yml files in the `plugins` subdirectory of your stash directory.

View File

@@ -0,0 +1,231 @@
// +build plugin_example
package common
import (
"context"
"errors"
"fmt"
"github.com/shurcooL/graphql"
"github.com/stashapp/stash/pkg/plugin/common/log"
)
const tagName = "Hawwwwt"
// graphql inputs and returns
type TagCreate struct {
ID graphql.ID `graphql:"id"`
}
type TagCreateInput struct {
Name graphql.String `graphql:"name" json:"name"`
}
type TagDestroyInput struct {
ID graphql.ID `graphql:"id" json:"id"`
}
type FindScenesResultType struct {
Count graphql.Int
Scenes []Scene
}
type Tag struct {
ID graphql.ID `graphql:"id"`
Name graphql.String `graphql:"name"`
}
type Scene struct {
ID graphql.ID
Tags []Tag
}
func (s Scene) getTagIds() []graphql.ID {
ret := []graphql.ID{}
for _, t := range s.Tags {
ret = append(ret, t.ID)
}
return ret
}
type FindFilterType struct {
PerPage *graphql.Int `graphql:"per_page" json:"per_page"`
Sort *graphql.String `graphql:"sort" json:"sort"`
}
type SceneUpdate struct {
ID graphql.ID `graphql:"id"`
}
type SceneUpdateInput struct {
ID graphql.ID `graphql:"id" json:"id"`
TagIds []graphql.ID `graphql:"tag_ids" json:"tag_ids"`
}
func getTagID(client *graphql.Client, create bool) (*graphql.ID, error) {
log.Info("Checking if tag exists already")
// see if tag exists already
var q struct {
AllTags []Tag `graphql:"allTags"`
}
err := client.Query(context.Background(), &q, nil)
if err != nil {
return nil, fmt.Errorf("Error getting tags: %s\n", err.Error())
}
for _, t := range q.AllTags {
if t.Name == tagName {
id := t.ID
return &id, nil
}
}
if !create {
log.Info("Not found and not creating")
return nil, nil
}
// create the tag
var m struct {
TagCreate TagCreate `graphql:"tagCreate(input: $s)"`
}
input := TagCreateInput{
Name: tagName,
}
vars := map[string]interface{}{
"s": input,
}
log.Info("Creating new tag")
err = client.Mutate(context.Background(), &m, vars)
if err != nil {
return nil, fmt.Errorf("Error mutating scene: %s\n", err.Error())
}
return &m.TagCreate.ID, nil
}
func findRandomScene(client *graphql.Client) (*Scene, error) {
// get a random scene
var q struct {
FindScenes FindScenesResultType `graphql:"findScenes(filter: $c)"`
}
pp := graphql.Int(1)
sort := graphql.String("random")
filterInput := &FindFilterType{
PerPage: &pp,
Sort: &sort,
}
vars := map[string]interface{}{
"c": filterInput,
}
log.Info("Finding a random scene")
err := client.Query(context.Background(), &q, vars)
if err != nil {
return nil, fmt.Errorf("Error getting random scene: %s\n", err.Error())
}
if q.FindScenes.Count == 0 {
return nil, nil
}
return &q.FindScenes.Scenes[0], nil
}
func addTagId(tagIds []graphql.ID, tagId graphql.ID) []graphql.ID {
for _, t := range tagIds {
if t == tagId {
return tagIds
}
}
tagIds = append(tagIds, tagId)
return tagIds
}
func AddTag(client *graphql.Client) error {
tagID, err := getTagID(client, true)
if err != nil {
return err
}
scene, err := findRandomScene(client)
if err != nil {
return err
}
if scene == nil {
return errors.New("no scenes to add tag to")
}
var m struct {
SceneUpdate SceneUpdate `graphql:"sceneUpdate(input: $s)"`
}
input := SceneUpdateInput{
ID: scene.ID,
TagIds: scene.getTagIds(),
}
input.TagIds = addTagId(input.TagIds, *tagID)
vars := map[string]interface{}{
"s": input,
}
log.Infof("Adding tag to scene %v", scene.ID)
err = client.Mutate(context.Background(), &m, vars)
if err != nil {
return fmt.Errorf("Error mutating scene: %s", err.Error())
}
return nil
}
func RemoveTag(client *graphql.Client) error {
tagID, err := getTagID(client, false)
if err != nil {
return err
}
if tagID == nil {
log.Info("Tag does not exist. Nothing to remove")
return nil
}
// destroy the tag
var m struct {
TagDestroy bool `graphql:"tagDestroy(input: $s)"`
}
input := TagDestroyInput{
ID: *tagID,
}
vars := map[string]interface{}{
"s": input,
}
log.Info("Destroying tag")
err = client.Mutate(context.Background(), &m, vars)
if err != nil {
return fmt.Errorf("Error destroying tag: %s", err.Error())
}
return nil
}

View File

@@ -0,0 +1,28 @@
# example plugin config
name: Hawwwwt Tagger (Raw edition)
description: Ultimate Hawwwwt tagging utility (using raw interface).
version: 1.0
url: http://www.github.com/stashapp/stash
exec:
- plugin_goraw
interface: raw
tasks:
- name: Add hawwwwt tag to random scene
description: Creates a "Hawwwwt" tag if not present and adds to a random scene.
defaultArgs:
mode: add
- name: Remove hawwwwt tag from system
description: Removes the "Hawwwwt" tag from all scenes and deletes the tag.
defaultArgs:
mode: remove
- name: Indefinite task
description: Sleeps indefinitely - interruptable
# we'll try command-line argument for this one
execArgs:
- indef
- "{pluginDir}"
- name: Long task
description: Sleeps for 100 seconds - interruptable
defaultArgs:
mode: long

View File

@@ -0,0 +1,106 @@
// +build plugin_example
package main
import (
"encoding/json"
"io/ioutil"
"os"
"time"
exampleCommon "github.com/stashapp/stash/pkg/plugin/examples/common"
"github.com/stashapp/stash/pkg/plugin/common"
"github.com/stashapp/stash/pkg/plugin/common/log"
"github.com/stashapp/stash/pkg/plugin/util"
)
// raw plugins may accept the plugin input from stdin, or they can elect
// to ignore it entirely. In this case it optionally reads from the
// command-line parameters.
func main() {
input := common.PluginInput{}
if len(os.Args) < 2 {
inData, _ := ioutil.ReadAll(os.Stdin)
log.Debugf("Raw input: %s", string(inData))
decodeErr := json.Unmarshal(inData, &input)
if decodeErr != nil {
panic("missing mode argument")
}
} else {
log.Debug("Using command line inputs")
mode := os.Args[1]
log.Debugf("Command line inputs: %v", os.Args[1:])
input.Args = common.ArgsMap{
"mode": mode,
}
// just some hard-coded values
input.ServerConnection = common.StashServerConnection{
Scheme: "http",
Port: 9999,
}
}
output := common.PluginOutput{}
Run(input, &output)
out, _ := json.Marshal(output)
os.Stdout.WriteString(string(out))
}
func Run(input common.PluginInput, output *common.PluginOutput) error {
modeArg := input.Args.String("mode")
var err error
if modeArg == "" || modeArg == "add" {
client := util.NewClient(input.ServerConnection)
err = exampleCommon.AddTag(client)
} else if modeArg == "remove" {
client := util.NewClient(input.ServerConnection)
err = exampleCommon.RemoveTag(client)
} else if modeArg == "long" {
err = doLongTask()
} else if modeArg == "indef" {
err = doIndefiniteTask()
}
if err != nil {
errStr := err.Error()
*output = common.PluginOutput{
Error: &errStr,
}
return nil
}
outputStr := "ok"
*output = common.PluginOutput{
Output: &outputStr,
}
return nil
}
func doLongTask() error {
const total = 100
upTo := 0
log.Info("Doing long task")
for upTo < total {
time.Sleep(time.Second)
log.Progress(float64(upTo) / float64(total))
upTo++
}
return nil
}
func doIndefiniteTask() error {
log.Warn("Sleeping indefinitely")
for {
time.Sleep(time.Second)
}
}

View File

@@ -0,0 +1,26 @@
# example plugin config
name: Hawwwwt Tagger
description: Ultimate Hawwwwt tagging utility.
version: 1.0
url: http://www.github.com/stashapp/stash
exec:
- plugin_gorpc
interface: rpc
tasks:
- name: Add hawwwwt tag to random scene
description: Creates a "Hawwwwt" tag if not present and adds to a random scene.
defaultArgs:
mode: add
- name: Remove hawwwwt tag from system
description: Removes the "Hawwwwt" tag from all scenes and deletes the tag.
defaultArgs:
mode: remove
- name: Indefinite task
description: Sleeps indefinitely - interruptable
defaultArgs:
mode: indef
- name: Long task
description: Sleeps for 100 seconds - interruptable
defaultArgs:
mode: long

View File

@@ -0,0 +1,97 @@
// +build plugin_example
package main
import (
"time"
exampleCommon "github.com/stashapp/stash/pkg/plugin/examples/common"
"github.com/stashapp/stash/pkg/plugin/common"
"github.com/stashapp/stash/pkg/plugin/common/log"
"github.com/stashapp/stash/pkg/plugin/util"
)
func main() {
// serves the plugin, providing an object that satisfies the
// common.RPCRunner interface
err := common.ServePlugin(&api{})
if err != nil {
panic(err)
}
}
type api struct {
stopping bool
}
func (a *api) Stop(input struct{}, output *bool) error {
log.Info("Stopping...")
a.stopping = true
*output = true
return nil
}
// Run is the main work function of the plugin. It interprets the input and
// acts accordingly.
func (a *api) Run(input common.PluginInput, output *common.PluginOutput) error {
modeArg := input.Args.String("mode")
var err error
if modeArg == "" || modeArg == "add" {
client := util.NewClient(input.ServerConnection)
err = exampleCommon.AddTag(client)
} else if modeArg == "remove" {
client := util.NewClient(input.ServerConnection)
err = exampleCommon.RemoveTag(client)
} else if modeArg == "long" {
err = a.doLongTask()
} else if modeArg == "indef" {
err = a.doIndefiniteTask()
}
if err != nil {
errStr := err.Error()
*output = common.PluginOutput{
Error: &errStr,
}
return nil
}
outputStr := "ok"
*output = common.PluginOutput{
Output: &outputStr,
}
return nil
}
func (a *api) doLongTask() error {
const total = 100
upTo := 0
log.Info("Doing long task")
for upTo < total {
time.Sleep(time.Second)
if a.stopping {
return nil
}
log.Progress(float64(upTo) / float64(total))
upTo++
}
return nil
}
func (a *api) doIndefiniteTask() error {
log.Warn("Sleeping indefinitely")
for {
time.Sleep(time.Second)
if a.stopping {
return nil
}
}
return nil
}