Skip to content

Commit

Permalink
Add support for disabling plugins (stashapp#4141)
Browse files Browse the repository at this point in the history
* Move timestamp to own file
* Backend changes
* UI changes
  • Loading branch information
WithoutPants authored and halkeye committed Sep 1, 2024
1 parent ec0db30 commit 6127fb8
Show file tree
Hide file tree
Showing 18 changed files with 260 additions and 62 deletions.
2 changes: 2 additions & 0 deletions gqlgen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ models:
model: github.com/99designs/gqlgen/graphql.Int64
Timestamp:
model: github.com/stashapp/stash/internal/api.Timestamp
BoolMap:
model: github.com/stashapp/stash/internal/api.BoolMap
# define to force resolvers
Image:
model: github.com/stashapp/stash/pkg/models.Image
Expand Down
4 changes: 4 additions & 0 deletions graphql/documents/mutations/plugins.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ mutation RunPluginTask(
) {
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
}

mutation SetPluginsEnabled($enabledMap: BoolMap!) {
setPluginsEnabled(enabledMap: $enabledMap)
}
2 changes: 2 additions & 0 deletions graphql/documents/queries/plugins.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ query Plugins {
plugins {
id
name
enabled
description
url
version
Expand All @@ -26,6 +27,7 @@ query PluginTasks {
plugin {
id
name
enabled
}
}
}
6 changes: 6 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ type Mutation {
"Reload scrapers"
reloadScrapers: Boolean!

"""
Enable/disable plugins - enabledMap is a map of plugin IDs to enabled booleans.
Plugins not in the map are not affected.
"""
setPluginsEnabled(enabledMap: BoolMap!): Boolean!

"Run plugin task. Returns the job ID"
runPluginTask(
plugin_id: ID!
Expand Down
2 changes: 2 additions & 0 deletions graphql/schema/types/plugin.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ type Plugin {
url: String
version: String

enabled: Boolean!

tasks: [PluginTask!]
hooks: [PluginHook!]
}
Expand Down
3 changes: 3 additions & 0 deletions graphql/schema/types/scalars.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ scalar Timestamp
# generic JSON object
scalar Map

# string, boolean map
scalar BoolMap

scalar Any

scalar Int64
38 changes: 38 additions & 0 deletions internal/api/bool_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package api

import (
"encoding/json"
"fmt"
"io"

"github.com/99designs/gqlgen/graphql"
)

func MarshalBoolMap(val map[string]bool) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
panic(err)
}
})
}

func UnmarshalBoolMap(v interface{}) (map[string]bool, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%T is not a map", v)
}

result := make(map[string]bool)
for k, v := range m {
key := k
val, ok := v.(bool)
if !ok {
return nil, fmt.Errorf("key %s (%T) is not a bool", k, v)
}

result[key] = val
}

return result, nil
}
53 changes: 0 additions & 53 deletions internal/api/models.go
Original file line number Diff line number Diff line change
@@ -1,64 +1,11 @@
package api

import (
"errors"
"fmt"
"io"
"strconv"
"time"

"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)

type BaseFile interface{}

type GalleryFile struct {
*models.BaseFile
}

var ErrTimestamp = errors.New("cannot parse Timestamp")

func MarshalTimestamp(t time.Time) graphql.Marshaler {
if t.IsZero() {
return graphql.Null
}

return graphql.WriterFunc(func(w io.Writer) {
_, err := io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano)))
if err != nil {
logger.Warnf("could not marshal timestamp: %v", err)
}
})
}

func UnmarshalTimestamp(v interface{}) (time.Time, error) {
if tmpStr, ok := v.(string); ok {
if len(tmpStr) == 0 {
return time.Time{}, fmt.Errorf("%w: empty string", ErrTimestamp)
}

switch tmpStr[0] {
case '>', '<':
d, err := time.ParseDuration(tmpStr[1:])
if err != nil {
return time.Time{}, fmt.Errorf("%w: cannot parse %v-duration: %v", ErrTimestamp, tmpStr[0], err)
}
t := time.Now()
// Compute point in time:
if tmpStr[0] == '<' {
t = t.Add(-d)
} else {
t = t.Add(d)
}

return t, nil
}

return utils.ParseDateStringAsTime(tmpStr)
}

return time.Time{}, fmt.Errorf("%w: not a string", ErrTimestamp)
}
31 changes: 31 additions & 0 deletions internal/api/resolver_mutation_plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"

"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
)

func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*plugin.PluginArgInput) (string, error) {
Expand All @@ -22,3 +24,32 @@ func (r *mutationResolver) ReloadPlugins(ctx context.Context) (bool, error) {

return true, nil
}

func (r *mutationResolver) SetPluginsEnabled(ctx context.Context, enabledMap map[string]bool) (bool, error) {
c := config.GetInstance()

existingDisabled := c.GetDisabledPlugins()
var newDisabled []string

// remove plugins that are no longer disabled
for _, disabledID := range existingDisabled {
if enabled, found := enabledMap[disabledID]; !enabled || !found {
newDisabled = append(newDisabled, disabledID)
}
}

// add plugins that are newly disabled
for pluginID, enabled := range enabledMap {
if !enabled {
newDisabled = stringslice.StrAppendUnique(newDisabled, pluginID)
}
}

c.Set(config.DisabledPlugins, newDisabled)

if err := c.Write(); err != nil {
return false, err
}

return true, nil
}
8 changes: 8 additions & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ func cssHandler(c *config.Instance, pluginCache *plugin.Cache) func(w http.Respo
var paths []string

for _, p := range pluginCache.ListPlugins() {
if !p.Enabled {
continue
}

paths = append(paths, p.UI.CSS...)
}

Expand All @@ -318,6 +322,10 @@ func javascriptHandler(c *config.Instance, pluginCache *plugin.Cache) func(w htt
var paths []string

for _, p := range pluginCache.ListPlugins() {
if !p.Enabled {
continue
}

paths = append(paths, p.UI.Javascript...)
}

Expand Down
57 changes: 57 additions & 0 deletions internal/api/timestamp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package api

import (
"errors"
"fmt"
"io"
"strconv"
"time"

"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/utils"
)

var ErrTimestamp = errors.New("cannot parse Timestamp")

func MarshalTimestamp(t time.Time) graphql.Marshaler {
if t.IsZero() {
return graphql.Null
}

return graphql.WriterFunc(func(w io.Writer) {
_, err := io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano)))
if err != nil {
logger.Warnf("could not marshal timestamp: %v", err)
}
})
}

func UnmarshalTimestamp(v interface{}) (time.Time, error) {
if tmpStr, ok := v.(string); ok {
if len(tmpStr) == 0 {
return time.Time{}, fmt.Errorf("%w: empty string", ErrTimestamp)
}

switch tmpStr[0] {
case '>', '<':
d, err := time.ParseDuration(tmpStr[1:])
if err != nil {
return time.Time{}, fmt.Errorf("%w: cannot parse %v-duration: %v", ErrTimestamp, tmpStr[0], err)
}
t := time.Now()
// Compute point in time:
if tmpStr[0] == '<' {
t = t.Add(-d)
} else {
t = t.Add(d)
}

return t, nil
}

return utils.ParseDateStringAsTime(tmpStr)
}

return time.Time{}, fmt.Errorf("%w: not a string", ErrTimestamp)
}
File renamed without changes.
7 changes: 6 additions & 1 deletion internal/manager/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ const (
PythonPath = "python_path"

// plugin options
PluginsPath = "plugins_path"
PluginsPath = "plugins_path"
DisabledPlugins = "plugins.disabled"

// i18n
Language = "language"
Expand Down Expand Up @@ -722,6 +723,10 @@ func (i *Instance) GetPluginsPath() string {
return i.getString(PluginsPath)
}

func (i *Instance) GetDisabledPlugins() []string {
return i.getStringSlice(DisabledPlugins)
}

func (i *Instance) GetPythonPath() string {
return i.getString(PythonPath)
}
Expand Down
Loading

0 comments on commit 6127fb8

Please sign in to comment.