mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Config Tweaks
Using viper for config management. Added configuration endpoint.
This commit is contained in:
37
pkg/api/resolver_query_configure.go
Normal file
37
pkg/api/resolver_query_configure.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *queryResolver) ConfigureGeneral(ctx context.Context, input *models.ConfigGeneralInput) (models.ConfigGeneralResult, error) {
|
||||
if input == nil {
|
||||
return makeConfigGeneralResult(), fmt.Errorf("nil input")
|
||||
}
|
||||
|
||||
if len(input.Stashes) > 0 {
|
||||
for _, stashPath := range input.Stashes {
|
||||
exists, err := utils.DirExists(stashPath)
|
||||
if !exists {
|
||||
return makeConfigGeneralResult(), err
|
||||
}
|
||||
}
|
||||
config.Set(config.Stash, input.Stashes)
|
||||
}
|
||||
|
||||
if err := config.Write(); err != nil {
|
||||
return makeConfigGeneralResult(), err
|
||||
}
|
||||
|
||||
return makeConfigGeneralResult(), nil
|
||||
}
|
||||
|
||||
func makeConfigGeneralResult() models.ConfigGeneralResult {
|
||||
return models.ConfigGeneralResult{
|
||||
Stashes: config.GetStashPaths(),
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/cors"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
"github.com/stashapp/stash/pkg/manager/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"net/http"
|
||||
@@ -29,6 +28,7 @@ const httpsPort = "9999"
|
||||
|
||||
var certsBox *packr.Box
|
||||
var uiBox *packr.Box
|
||||
|
||||
//var legacyUiBox *packr.Box
|
||||
var setupUIBox *packr.Box
|
||||
|
||||
@@ -99,41 +99,44 @@ func Start() {
|
||||
http.Error(w, fmt.Sprintf("error: %s", err), 500)
|
||||
}
|
||||
stash := filepath.Clean(r.Form.Get("stash"))
|
||||
generated := filepath.Clean(r.Form.Get("generated"))
|
||||
metadata := filepath.Clean(r.Form.Get("metadata"))
|
||||
cache := filepath.Clean(r.Form.Get("cache"))
|
||||
//downloads := filepath.Clean(r.Form.Get("downloads")) // TODO
|
||||
downloads := filepath.Join(metadata, "downloads")
|
||||
|
||||
exists, _ := utils.FileExists(stash)
|
||||
fileInfo, _ := os.Stat(stash)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
exists, _ := utils.DirExists(stash)
|
||||
if !exists || stash == "." {
|
||||
http.Error(w, fmt.Sprintf("the stash path either doesn't exist, or is not a directory <%s>. Go back and try again.", stash), 500)
|
||||
return
|
||||
}
|
||||
|
||||
exists, _ = utils.FileExists(metadata)
|
||||
fileInfo, _ = os.Stat(metadata)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
exists, _ = utils.DirExists(generated)
|
||||
if !exists || generated == "." {
|
||||
http.Error(w, fmt.Sprintf("the generated path either doesn't exist, or is not a directory <%s>. Go back and try again.", generated), 500)
|
||||
return
|
||||
}
|
||||
|
||||
exists, _ = utils.DirExists(metadata)
|
||||
if !exists || metadata == "." {
|
||||
http.Error(w, fmt.Sprintf("the metadata path either doesn't exist, or is not a directory <%s> Go back and try again.", metadata), 500)
|
||||
return
|
||||
}
|
||||
|
||||
exists, _ = utils.FileExists(cache)
|
||||
fileInfo, _ = os.Stat(cache)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
exists, _ = utils.DirExists(cache)
|
||||
if !exists || cache == "." {
|
||||
http.Error(w, fmt.Sprintf("the cache path either doesn't exist, or is not a directory <%s> Go back and try again.", cache), 500)
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.Mkdir(downloads, 0755)
|
||||
|
||||
config := &jsonschema.Config{
|
||||
Stash: stash,
|
||||
Metadata: metadata,
|
||||
Cache: cache,
|
||||
Downloads: downloads,
|
||||
}
|
||||
if err := manager.GetInstance().SaveConfig(config); err != nil {
|
||||
config.Set(config.Stash, stash)
|
||||
config.Set(config.Generated, generated)
|
||||
config.Set(config.Metadata, metadata)
|
||||
config.Set(config.Cache, cache)
|
||||
config.Set(config.Downloads, downloads)
|
||||
if err := config.Write(); err != nil {
|
||||
http.Error(w, fmt.Sprintf("there was an error saving the config file: %s", err), 500)
|
||||
return
|
||||
}
|
||||
@@ -220,7 +223,7 @@ func ConfigCheckMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ext := path.Ext(r.URL.Path)
|
||||
shouldRedirect := ext == "" && r.Method == "GET" && r.URL.Path != "/init"
|
||||
if !manager.HasValidConfig() && shouldRedirect {
|
||||
if !config.IsValid() && shouldRedirect {
|
||||
if !strings.HasPrefix(r.URL.Path, "/setup") {
|
||||
http.Redirect(w, r, "/setup", 301)
|
||||
return
|
||||
|
||||
47
pkg/manager/config/config.go
Normal file
47
pkg/manager/config/config.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const Stash = "stash"
|
||||
const Cache = "cache"
|
||||
const Generated = "generated"
|
||||
const Metadata = "metadata"
|
||||
const Downloads = "downloads"
|
||||
|
||||
const Database = "database"
|
||||
|
||||
func Set(key string, value interface{}) {
|
||||
viper.Set(key, value)
|
||||
}
|
||||
|
||||
func Write() error {
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func GetStashPaths() []string {
|
||||
return viper.GetStringSlice(Stash)
|
||||
}
|
||||
|
||||
func GetCachePath() string {
|
||||
return viper.GetString(Cache)
|
||||
}
|
||||
|
||||
func GetGeneratedPath() string {
|
||||
return viper.GetString(Generated)
|
||||
}
|
||||
|
||||
func GetMetadataPath() string {
|
||||
return viper.GetString(Metadata)
|
||||
}
|
||||
|
||||
func GetDatabasePath() string {
|
||||
return viper.GetString(Database)
|
||||
}
|
||||
|
||||
func IsValid() bool {
|
||||
setPaths := viper.IsSet(Stash) && viper.IsSet(Cache) && viper.IsSet(Generated) && viper.IsSet(Metadata)
|
||||
// TODO: check valid paths
|
||||
return setPaths
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
|
||||
func (g *PreviewGenerator) Generate() error {
|
||||
instance.Paths.Generated.EmptyTmpDir()
|
||||
logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path)
|
||||
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
||||
|
||||
if err := g.generateConcatFile(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -49,7 +49,7 @@ func NewSpriteGenerator(videoFile ffmpeg.VideoFile, imageOutputPath string, vttO
|
||||
|
||||
func (g *SpriteGenerator) Generate() error {
|
||||
instance.Paths.Generated.EmptyTmpDir()
|
||||
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
||||
|
||||
if err := g.generateSpriteImage(&encoder); err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package jsonschema
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Stash string `json:"stash"`
|
||||
Metadata string `json:"metadata"`
|
||||
// Generated string `json:"generated"` // TODO: Generated directory instead of metadata
|
||||
Cache string `json:"cache"`
|
||||
Downloads string `json:"downloads"`
|
||||
}
|
||||
|
||||
func LoadConfigFile(file string) *Config {
|
||||
var config Config
|
||||
configFile, err := os.Open(file)
|
||||
defer configFile.Close()
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
jsonParser := json.NewDecoder(configFile)
|
||||
parseError := jsonParser.Decode(&config)
|
||||
if parseError != nil {
|
||||
logger.Errorf("config file parse error (ignore on first launch): %s", parseError)
|
||||
}
|
||||
return &config
|
||||
}
|
||||
|
||||
func SaveConfigFile(filePath string, config *Config) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("config must not be nil")
|
||||
}
|
||||
return marshalToFile(filePath, config)
|
||||
}
|
||||
@@ -1,19 +1,24 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stashapp/stash/pkg/ffmpeg"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/manager/paths"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type singleton struct {
|
||||
Status JobStatus
|
||||
Paths *paths.Paths
|
||||
StaticPaths *paths.StaticPathsType
|
||||
JSON *jsonUtils
|
||||
Status JobStatus
|
||||
Paths *paths.Paths
|
||||
JSON *jsonUtils
|
||||
|
||||
FFMPEGPath string
|
||||
FFProbePath string
|
||||
}
|
||||
|
||||
var instance *singleton
|
||||
@@ -26,16 +31,15 @@ func GetInstance() *singleton {
|
||||
|
||||
func Initialize() *singleton {
|
||||
once.Do(func() {
|
||||
_ = utils.EnsureDir(paths.StaticPaths.ConfigDirectory)
|
||||
configFile := jsonschema.LoadConfigFile(paths.StaticPaths.ConfigFile)
|
||||
_ = utils.EnsureDir(paths.GetConfigDirectory())
|
||||
initConfig()
|
||||
instance = &singleton{
|
||||
Status: Idle,
|
||||
Paths: paths.NewPaths(configFile),
|
||||
StaticPaths: &paths.StaticPaths,
|
||||
JSON: &jsonUtils{},
|
||||
Status: Idle,
|
||||
Paths: paths.NewPaths(),
|
||||
JSON: &jsonUtils{},
|
||||
}
|
||||
|
||||
instance.refreshConfig(configFile)
|
||||
instance.refreshConfig()
|
||||
|
||||
initFFMPEG()
|
||||
})
|
||||
@@ -43,11 +47,45 @@ func Initialize() *singleton {
|
||||
return instance
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
// The config file is called config. Leave off the file extension.
|
||||
viper.SetConfigName("config")
|
||||
|
||||
viper.AddConfigPath("$HOME/.stash") // Look for the config in the home directory
|
||||
viper.AddConfigPath(".") // Look for config in the working directory
|
||||
|
||||
viper.SetDefault(config.Database, paths.GetDefaultDatabaseFilePath())
|
||||
|
||||
// Set generated to the metadata path for backwards compat
|
||||
if !viper.IsSet(config.Generated) {
|
||||
viper.SetDefault(config.Generated, viper.GetString(config.Metadata))
|
||||
}
|
||||
|
||||
err := viper.ReadInConfig() // Find and read the config file
|
||||
if err != nil { // Handle errors reading the config file
|
||||
_ = utils.Touch(paths.GetDefaultConfigFilePath())
|
||||
if err = viper.ReadInConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for changes
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
fmt.Println("Config file changed:", e.Name)
|
||||
instance.refreshConfig()
|
||||
})
|
||||
|
||||
//viper.Set("stash", []string{"/", "/stuff"})
|
||||
//viper.WriteConfig()
|
||||
}
|
||||
|
||||
func initFFMPEG() {
|
||||
ffmpegPath, ffprobePath := ffmpeg.GetPaths(instance.StaticPaths.ConfigDirectory)
|
||||
configDirectory := paths.GetConfigDirectory()
|
||||
ffmpegPath, ffprobePath := ffmpeg.GetPaths(configDirectory)
|
||||
if ffmpegPath == "" || ffprobePath == "" {
|
||||
logger.Infof("couldn't find FFMPEG, attempting to download it")
|
||||
if err := ffmpeg.Download(instance.StaticPaths.ConfigDirectory); err != nil {
|
||||
if err := ffmpeg.Download(configDirectory); err != nil {
|
||||
msg := `Unable to locate / automatically download FFMPEG
|
||||
|
||||
Check the readme for download links.
|
||||
@@ -55,40 +93,18 @@ The FFMPEG and FFProbe binaries should be placed in %s
|
||||
|
||||
The error was: %s
|
||||
`
|
||||
logger.Fatalf(msg, instance.StaticPaths.ConfigDirectory, err)
|
||||
logger.Fatalf(msg, configDirectory, err)
|
||||
}
|
||||
}
|
||||
|
||||
instance.StaticPaths.FFMPEG = ffmpegPath
|
||||
instance.StaticPaths.FFProbe = ffprobePath
|
||||
// TODO: is this valid after download?
|
||||
instance.FFMPEGPath = ffmpegPath
|
||||
instance.FFProbePath = ffprobePath
|
||||
}
|
||||
|
||||
func HasValidConfig() bool {
|
||||
configFileExists, _ := utils.FileExists(instance.StaticPaths.ConfigFile) // TODO: Verify JSON is correct
|
||||
if configFileExists && instance.Paths.Config != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *singleton) SaveConfig(config *jsonschema.Config) error {
|
||||
if err := jsonschema.SaveConfigFile(s.StaticPaths.ConfigFile, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload the config
|
||||
s.refreshConfig(config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *singleton) refreshConfig(config *jsonschema.Config) {
|
||||
if config == nil {
|
||||
config = jsonschema.LoadConfigFile(s.StaticPaths.ConfigFile)
|
||||
}
|
||||
s.Paths = paths.NewPaths(config)
|
||||
|
||||
if HasValidConfig() {
|
||||
func (s *singleton) refreshConfig() {
|
||||
s.Paths = paths.NewPaths()
|
||||
if config.IsValid() {
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Screenshots)
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Vtt)
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Markers)
|
||||
|
||||
@@ -3,6 +3,7 @@ package manager
|
||||
import (
|
||||
"github.com/bmatcuk/doublestar"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"path/filepath"
|
||||
@@ -18,12 +19,16 @@ func (s *singleton) Scan() {
|
||||
go func() {
|
||||
defer s.returnToIdleState()
|
||||
|
||||
globPath := filepath.Join(s.Paths.Config.Stash, "**/*.{zip,m4v,mp4,mov,wmv}")
|
||||
globResults, _ := doublestar.Glob(globPath)
|
||||
logger.Infof("Starting scan of %d files", len(globResults))
|
||||
var results []string
|
||||
for _, path := range config.GetStashPaths() {
|
||||
globPath := filepath.Join(path, "**/*.{zip,m4v,mp4,mov,wmv}")
|
||||
globResults, _ := doublestar.Glob(globPath)
|
||||
results = append(results, globResults...)
|
||||
}
|
||||
logger.Infof("Starting scan of %d files", len(results))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, path := range globResults {
|
||||
for _, path := range results {
|
||||
wg.Add(1)
|
||||
task := ScanTask{FilePath: path}
|
||||
go task.Start(&wg)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/pkg/manager/jsonschema"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Paths struct {
|
||||
Config *jsonschema.Config
|
||||
Generated *generatedPaths
|
||||
JSON *jsonPaths
|
||||
|
||||
@@ -14,14 +14,33 @@ type Paths struct {
|
||||
SceneMarkers *sceneMarkerPaths
|
||||
}
|
||||
|
||||
func NewPaths(config *jsonschema.Config) *Paths {
|
||||
func NewPaths() *Paths {
|
||||
p := Paths{}
|
||||
p.Config = config
|
||||
p.Generated = newGeneratedPaths(p)
|
||||
p.JSON = newJSONPaths(p)
|
||||
p.Generated = newGeneratedPaths()
|
||||
p.JSON = newJSONPaths()
|
||||
|
||||
p.Gallery = newGalleryPaths(p.Config)
|
||||
p.Gallery = newGalleryPaths()
|
||||
p.Scene = newScenePaths(p)
|
||||
p.SceneMarkers = newSceneMarkerPaths(p)
|
||||
return &p
|
||||
}
|
||||
|
||||
func GetHomeDirectory() string {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return currentUser.HomeDir
|
||||
}
|
||||
|
||||
func GetConfigDirectory() string {
|
||||
return filepath.Join(GetHomeDirectory(), ".stash")
|
||||
}
|
||||
|
||||
func GetDefaultDatabaseFilePath() string {
|
||||
return filepath.Join(GetConfigDirectory(), "stash-go.sqlite")
|
||||
}
|
||||
|
||||
func GetDefaultConfigFilePath() string {
|
||||
return filepath.Join(GetConfigDirectory(), "config.yml")
|
||||
}
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/pkg/manager/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type galleryPaths struct {
|
||||
config *jsonschema.Config
|
||||
}
|
||||
type galleryPaths struct{}
|
||||
|
||||
func newGalleryPaths(c *jsonschema.Config) *galleryPaths {
|
||||
gp := galleryPaths{}
|
||||
gp.config = c
|
||||
return &gp
|
||||
func newGalleryPaths() *galleryPaths {
|
||||
return &galleryPaths{}
|
||||
}
|
||||
|
||||
func (gp *galleryPaths) GetExtractedPath(checksum string) string {
|
||||
return filepath.Join(gp.config.Cache, checksum)
|
||||
return filepath.Join(config.GetCachePath(), checksum)
|
||||
}
|
||||
|
||||
func (gp *galleryPaths) GetExtractedFilePath(checksum string, fileName string) string {
|
||||
return filepath.Join(gp.config.Cache, checksum, fileName)
|
||||
return filepath.Join(config.GetCachePath(), checksum, fileName)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -13,13 +14,13 @@ type generatedPaths struct {
|
||||
Tmp string
|
||||
}
|
||||
|
||||
func newGeneratedPaths(p Paths) *generatedPaths {
|
||||
func newGeneratedPaths() *generatedPaths {
|
||||
gp := generatedPaths{}
|
||||
gp.Screenshots = filepath.Join(p.Config.Metadata, "screenshots")
|
||||
gp.Vtt = filepath.Join(p.Config.Metadata, "vtt")
|
||||
gp.Markers = filepath.Join(p.Config.Metadata, "markers")
|
||||
gp.Transcodes = filepath.Join(p.Config.Metadata, "transcodes")
|
||||
gp.Tmp = filepath.Join(p.Config.Metadata, "tmp")
|
||||
gp.Screenshots = filepath.Join(config.GetGeneratedPath(), "screenshots")
|
||||
gp.Vtt = filepath.Join(config.GetGeneratedPath(), "vtt")
|
||||
gp.Markers = filepath.Join(config.GetGeneratedPath(), "markers")
|
||||
gp.Transcodes = filepath.Join(config.GetGeneratedPath(), "transcodes")
|
||||
gp.Tmp = filepath.Join(config.GetGeneratedPath(), "tmp")
|
||||
return &gp
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -14,14 +15,14 @@ type jsonPaths struct {
|
||||
Studios string
|
||||
}
|
||||
|
||||
func newJSONPaths(p Paths) *jsonPaths {
|
||||
func newJSONPaths() *jsonPaths {
|
||||
jp := jsonPaths{}
|
||||
jp.MappingsFile = filepath.Join(p.Config.Metadata, "mappings.json")
|
||||
jp.ScrapedFile = filepath.Join(p.Config.Metadata, "scraped.json")
|
||||
jp.Performers = filepath.Join(p.Config.Metadata, "performers")
|
||||
jp.Scenes = filepath.Join(p.Config.Metadata, "scenes")
|
||||
jp.Galleries = filepath.Join(p.Config.Metadata, "galleries")
|
||||
jp.Studios = filepath.Join(p.Config.Metadata, "studios")
|
||||
jp.MappingsFile = filepath.Join(config.GetMetadataPath(), "mappings.json")
|
||||
jp.ScrapedFile = filepath.Join(config.GetMetadataPath(), "scraped.json")
|
||||
jp.Performers = filepath.Join(config.GetMetadataPath(), "performers")
|
||||
jp.Scenes = filepath.Join(config.GetMetadataPath(), "scenes")
|
||||
jp.Galleries = filepath.Join(config.GetMetadataPath(), "galleries")
|
||||
jp.Studios = filepath.Join(config.GetMetadataPath(), "studios")
|
||||
return &jp
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type StaticPathsType struct {
|
||||
ExecutionDirectory string
|
||||
ConfigDirectory string
|
||||
ConfigFile string
|
||||
DatabaseFile string
|
||||
|
||||
FFMPEG string
|
||||
FFProbe string
|
||||
}
|
||||
|
||||
var StaticPaths = StaticPathsType{
|
||||
ExecutionDirectory: getExecutionDirectory(),
|
||||
ConfigDirectory: getConfigDirectory(),
|
||||
ConfigFile: filepath.Join(getConfigDirectory(), "config.json"),
|
||||
DatabaseFile: filepath.Join(getConfigDirectory(), "stash-go.sqlite"),
|
||||
}
|
||||
|
||||
func getExecutionDirectory() string {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filepath.Dir(ex)
|
||||
}
|
||||
|
||||
func getHomeDirectory() string {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return currentUser.HomeDir
|
||||
}
|
||||
|
||||
func getConfigDirectory() string {
|
||||
return filepath.Join(getHomeDirectory(), ".stash")
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
||||
if err != nil {
|
||||
logger.Errorf("error reading video file: %s", err.Error())
|
||||
return
|
||||
@@ -35,7 +35,7 @@ func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
|
||||
markersFolder := filepath.Join(instance.Paths.Generated.Markers, t.Scene.Checksum)
|
||||
_ = utils.EnsureDir(markersFolder)
|
||||
|
||||
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
||||
for i, sceneMarker := range sceneMarkers {
|
||||
index := i + 1
|
||||
logger.Progressf("[generator] <%s> scene marker %d of %d", t.Scene.Checksum, index, len(sceneMarkers))
|
||||
|
||||
@@ -21,7 +21,7 @@ func (t *GeneratePreviewTask) Start(wg *sync.WaitGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
||||
if err != nil {
|
||||
logger.Errorf("error reading video file: %s", err.Error())
|
||||
return
|
||||
|
||||
@@ -19,7 +19,7 @@ func (t *GenerateSpriteTask) Start(wg *sync.WaitGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
||||
if err != nil {
|
||||
logger.Errorf("error reading video file: %s", err.Error())
|
||||
return
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/manager/jsonschema"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
@@ -33,7 +34,7 @@ func (t *ImportTask) Start(wg *sync.WaitGroup) {
|
||||
}
|
||||
t.Scraped = scraped
|
||||
|
||||
database.Reset(instance.StaticPaths.DatabaseFile)
|
||||
database.Reset(config.GetDatabasePath())
|
||||
|
||||
ctx := context.TODO()
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ func (t *ScanTask) scanGallery() {
|
||||
}
|
||||
|
||||
func (t *ScanTask) scanScene() {
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.FilePath)
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
return
|
||||
@@ -142,7 +142,7 @@ func (t *ScanTask) makeScreenshots(probeResult ffmpeg.VideoFile, checksum string
|
||||
}
|
||||
|
||||
func (t *ScanTask) makeScreenshot(probeResult ffmpeg.VideoFile, outputPath string, quality int, width int) {
|
||||
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
||||
options := ffmpeg.ScreenshotOptions{
|
||||
OutputPath: outputPath,
|
||||
Quality: quality,
|
||||
|
||||
@@ -26,7 +26,7 @@ func (t *GenerateTranscodeTask) Start(wg *sync.WaitGroup) {
|
||||
|
||||
logger.Infof("[transcode] <%s> scene has codec %s", t.Scene.Checksum, t.Scene.VideoCodec.String)
|
||||
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.StaticPaths.FFProbe, t.Scene.Path)
|
||||
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
||||
if err != nil {
|
||||
logger.Errorf("[transcode] error reading video file: %s", err.Error())
|
||||
return
|
||||
@@ -36,7 +36,7 @@ func (t *GenerateTranscodeTask) Start(wg *sync.WaitGroup) {
|
||||
options := ffmpeg.TranscodeOptions{
|
||||
OutputPath: outputPath,
|
||||
}
|
||||
encoder := ffmpeg.NewEncoder(instance.StaticPaths.FFMPEG)
|
||||
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
||||
encoder.Transcode(*videoFile, options)
|
||||
if err := os.Rename(outputPath, instance.Paths.Scene.GetTranscodePath(t.Scene.Checksum)); err != nil {
|
||||
logger.Errorf("[transcode] error generating transcode: %s", err.Error())
|
||||
|
||||
@@ -47,6 +47,10 @@ type DirectiveRoot struct {
|
||||
}
|
||||
|
||||
type ComplexityRoot struct {
|
||||
ConfigGeneralResult struct {
|
||||
Stashes func(childComplexity int) int
|
||||
}
|
||||
|
||||
FindGalleriesResultType struct {
|
||||
Count func(childComplexity int) int
|
||||
Galleries func(childComplexity int) int
|
||||
@@ -149,6 +153,7 @@ type ComplexityRoot struct {
|
||||
SceneMarkerTags func(childComplexity int, scene_id string) int
|
||||
ScrapeFreeones func(childComplexity int, performer_name string) int
|
||||
ScrapeFreeonesPerformerList func(childComplexity int, query string) int
|
||||
ConfigureGeneral func(childComplexity int, input *ConfigGeneralInput) int
|
||||
MetadataImport func(childComplexity int) int
|
||||
MetadataExport func(childComplexity int) int
|
||||
MetadataScan func(childComplexity int) int
|
||||
@@ -322,6 +327,7 @@ type QueryResolver interface {
|
||||
SceneMarkerTags(ctx context.Context, scene_id string) ([]SceneMarkerTag, error)
|
||||
ScrapeFreeones(ctx context.Context, performer_name string) (*ScrapedPerformer, error)
|
||||
ScrapeFreeonesPerformerList(ctx context.Context, query string) ([]string, error)
|
||||
ConfigureGeneral(ctx context.Context, input *ConfigGeneralInput) (ConfigGeneralResult, error)
|
||||
MetadataImport(ctx context.Context) (string, error)
|
||||
MetadataExport(ctx context.Context) (string, error)
|
||||
MetadataScan(ctx context.Context) (string, error)
|
||||
@@ -1060,6 +1066,34 @@ func (e *executableSchema) field_Query_scrapeFreeonesPerformerList_args(ctx cont
|
||||
|
||||
}
|
||||
|
||||
func (e *executableSchema) field_Query_configureGeneral_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *ConfigGeneralInput
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
var err error
|
||||
var ptr1 ConfigGeneralInput
|
||||
if tmp != nil {
|
||||
ptr1, err = UnmarshalConfigGeneralInput(tmp)
|
||||
arg0 = &ptr1
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if arg0 != nil {
|
||||
var err error
|
||||
arg0, err = e.ConfigGeneralInputMiddleware(ctx, arg0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
|
||||
}
|
||||
|
||||
func (e *executableSchema) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
args := map[string]interface{}{}
|
||||
var arg0 string
|
||||
@@ -1118,6 +1152,13 @@ func (e *executableSchema) Schema() *ast.Schema {
|
||||
func (e *executableSchema) Complexity(typeName, field string, childComplexity int, rawArgs map[string]interface{}) (int, bool) {
|
||||
switch typeName + "." + field {
|
||||
|
||||
case "ConfigGeneralResult.stashes":
|
||||
if e.complexity.ConfigGeneralResult.Stashes == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ConfigGeneralResult.Stashes(childComplexity), true
|
||||
|
||||
case "FindGalleriesResultType.count":
|
||||
if e.complexity.FindGalleriesResultType.Count == nil {
|
||||
break
|
||||
@@ -1755,6 +1796,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Query.ScrapeFreeonesPerformerList(childComplexity, args["query"].(string)), true
|
||||
|
||||
case "Query.configureGeneral":
|
||||
if e.complexity.Query.ConfigureGeneral == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := e.field_Query_configureGeneral_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ConfigureGeneral(childComplexity, args["input"].(*ConfigGeneralInput)), true
|
||||
|
||||
case "Query.metadataImport":
|
||||
if e.complexity.Query.MetadataImport == nil {
|
||||
break
|
||||
@@ -2383,6 +2436,64 @@ type executionContext struct {
|
||||
*executableSchema
|
||||
}
|
||||
|
||||
var configGeneralResultImplementors = []string{"ConfigGeneralResult"}
|
||||
|
||||
// nolint: gocyclo, errcheck, gas, goconst
|
||||
func (ec *executionContext) _ConfigGeneralResult(ctx context.Context, sel ast.SelectionSet, obj *ConfigGeneralResult) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ctx, sel, configGeneralResultImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
invalid := false
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("ConfigGeneralResult")
|
||||
case "stashes":
|
||||
out.Values[i] = ec._ConfigGeneralResult_stashes(ctx, field, obj)
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalid {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _ConfigGeneralResult_stashes(ctx context.Context, field graphql.CollectedField, obj *ConfigGeneralResult) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
Object: "ConfigGeneralResult",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Stashes, nil
|
||||
})
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.([]string)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
arr1 := make(graphql.Array, len(res))
|
||||
|
||||
for idx1 := range res {
|
||||
arr1[idx1] = func() graphql.Marshaler {
|
||||
return graphql.MarshalString(res[idx1])
|
||||
}()
|
||||
}
|
||||
|
||||
return arr1
|
||||
}
|
||||
|
||||
var findGalleriesResultTypeImplementors = []string{"FindGalleriesResultType"}
|
||||
|
||||
// nolint: gocyclo, errcheck, gas, goconst
|
||||
@@ -4824,6 +4935,15 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "configureGeneral":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
res = ec._Query_configureGeneral(ctx, field)
|
||||
if res == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "metadataImport":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
@@ -5712,6 +5832,41 @@ func (ec *executionContext) _Query_scrapeFreeonesPerformerList(ctx context.Conte
|
||||
return arr1
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _Query_configureGeneral(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
Object: "Query",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Query_configureGeneral_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
rctx.Args = args
|
||||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().ConfigureGeneral(rctx, args["input"].(*ConfigGeneralInput))
|
||||
})
|
||||
if resTmp == nil {
|
||||
if !ec.HasError(rctx) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(ConfigGeneralResult)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
return ec._ConfigGeneralResult(ctx, field.Selections, &res)
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _Query_metadataImport(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
@@ -10227,6 +10382,40 @@ func (ec *executionContext) ___Type_ofType(ctx context.Context, field graphql.Co
|
||||
return ec.___Type(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func UnmarshalConfigGeneralInput(v interface{}) (ConfigGeneralInput, error) {
|
||||
var it ConfigGeneralInput
|
||||
var asMap = v.(map[string]interface{})
|
||||
|
||||
for k, v := range asMap {
|
||||
switch k {
|
||||
case "stashes":
|
||||
var err error
|
||||
var rawIf1 []interface{}
|
||||
if v != nil {
|
||||
if tmp1, ok := v.([]interface{}); ok {
|
||||
rawIf1 = tmp1
|
||||
} else {
|
||||
rawIf1 = []interface{}{v}
|
||||
}
|
||||
}
|
||||
it.Stashes = make([]string, len(rawIf1))
|
||||
for idx1 := range rawIf1 {
|
||||
it.Stashes[idx1], err = graphql.UnmarshalString(rawIf1[idx1])
|
||||
}
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (e *executableSchema) ConfigGeneralInputMiddleware(ctx context.Context, obj *ConfigGeneralInput) (*ConfigGeneralInput, error) {
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
func UnmarshalFindFilterType(v interface{}) (FindFilterType, error) {
|
||||
var it FindFilterType
|
||||
var asMap = v.(map[string]interface{})
|
||||
@@ -11757,6 +11946,20 @@ input SceneFilterType {
|
||||
performer_id: ID
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Config
|
||||
#######################################
|
||||
|
||||
input ConfigGeneralInput {
|
||||
"""Array of file paths to content"""
|
||||
stashes: [String!]
|
||||
}
|
||||
|
||||
type ConfigGeneralResult {
|
||||
"""Array of file paths to content"""
|
||||
stashes: [String!]
|
||||
}
|
||||
|
||||
#############
|
||||
# Root Schema
|
||||
#############
|
||||
@@ -11807,6 +12010,9 @@ type Query {
|
||||
"""Scrape a list of performers from a query"""
|
||||
scrapeFreeonesPerformerList(query: String!): [String!]!
|
||||
|
||||
# Config
|
||||
configureGeneral(input: ConfigGeneralInput): ConfigGeneralResult!
|
||||
|
||||
# Metadata
|
||||
|
||||
"""Start an import. Returns the job ID"""
|
||||
|
||||
@@ -8,6 +8,16 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ConfigGeneralInput struct {
|
||||
// Array of file paths to content
|
||||
Stashes []string `json:"stashes"`
|
||||
}
|
||||
|
||||
type ConfigGeneralResult struct {
|
||||
// Array of file paths to content
|
||||
Stashes []string `json:"stashes"`
|
||||
}
|
||||
|
||||
type FindFilterType struct {
|
||||
Q *string `json:"q"`
|
||||
Page *int `json:"page"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/h2non/filetype"
|
||||
"github.com/h2non/filetype/types"
|
||||
"os"
|
||||
@@ -28,6 +29,27 @@ func FileExists(path string) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func DirExists(path string) (bool, error) {
|
||||
exists, _ := FileExists(path)
|
||||
fileInfo, _ := os.Stat(path)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
return false, fmt.Errorf("path either doesn't exist, or is not a directory <%s>", path)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func Touch(path string) error {
|
||||
var _, err = os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
var file, err = os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureDir(path string) error {
|
||||
exists, err := FileExists(path)
|
||||
if !exists {
|
||||
|
||||
Reference in New Issue
Block a user