mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Enable gocritic (#1848)
* Don't capitalize local variables
ValidCodecs -> validCodecs
* Capitalize deprecation markers
A deprecated marker should be capitalized.
* Use re.MustCompile for static regexes
If the regex fails to compile, it's a programmer error, and should be
treated as such. The regex is entirely static.
* Simplify else-if constructions
Rewrite
else { if cond {}}
to
else if cond {}
* Use a switch statement to analyze formats
Break an if-else chain. While here, simplify code flow.
Also introduce a proper static error for unsupported image formats,
paving the way for being able to check against the error.
* Rewrite ifElse chains into switch statements
The "Effective Go" https://golang.org/doc/effective_go#switch document
mentions it is more idiomatic to write if-else chains as switches when
it is possible.
Find all the plain rewrite occurrences in the code base and rewrite.
In some cases, the if-else chains are replaced by a switch scrutinizer.
That is, the code sequence
if x == 1 {
..
} else if x == 2 {
..
} else if x == 3 {
...
}
can be rewritten into
switch x {
case 1:
..
case 2:
..
case 3:
..
}
which is clearer for the compiler: it can decide if the switch is
better served by a jump-table then a branch-chain.
* Rewrite switches, introduce static errors
Introduce two new static errors:
* `ErrNotImplmented`
* `ErrNotSupported`
And use these rather than forming new generative errors whenever the
code is called. Code can now test on the errors (since they are static
and the pointers to them wont change).
Also rewrite ifElse chains into switches in this part of the code base.
* Introduce a StashBoxError in configuration
Since all stashbox errors are the same, treat them as such in the code
base. While here, rewrite an ifElse chain.
In the future, it might be beneifical to refactor configuration errors
into one error which can handle missing fields, which context the error
occurs in and so on. But for now, try to get an overview of the error
categories by hoisting them into static errors.
* Get rid of an else-block in transaction handling
If we succesfully `recover()`, we then always `panic()`. This means the
rest of the code is not reachable, so we can avoid having an else-block
here.
It also solves an ifElse-chain style check in the code base.
* Use strings.ReplaceAll
Rewrite
strings.Replace(s, o, n, -1)
into
strings.ReplaceAll(s, o, n)
To make it consistent and clear that we are doing an all-replace in the
string rather than replacing parts of it. It's more of a nitpick since
there are no implementation differences: the stdlib implementation is
just to supply -1.
* Rewrite via gocritic's assignOp
Statements of the form
x = x + e
is rewritten into
x += e
where applicable.
* Formatting
* Review comments handled
Stash-box is a proper noun.
Rewrite a switch into an if-chain which returns on the first error
encountered.
* Use context.TODO() over context.Background()
Patch in the same vein as everything else: use the TODO() marker so we
can search for it later and link it into the context tree/tentacle once
it reaches down to this level in the code base.
* Tell the linter to ignore a section in manager_tasks.go
The section is less readable, so mark it with a nolint for now. Because
the rewrite enables a ifElseChain, also mark that as nolint for now.
* Use strings.ReplaceAll over strings.Replace
* Apply an ifElse rewrite
else { if .. { .. } } rewrite into else if { .. }
* Use switch-statements over ifElseChains
Rewrite chains of if-else into switch statements. Where applicable,
add an early nil-guard to simplify case analysis. Also, in
ScanTask's Start(..), invert the logic to outdent the whole block, and
help the reader: if it's not a scene, the function flow is now far more
local to the top of the function, and it's clear that the rest of the
function has to do with scene management.
* Enable gocritic on the code base.
Disable appendAssign for now since we aren't passing that check yet.
* Document the nolint additions
* Document StashBoxBatchPerformerTagInput
This commit is contained in:
@@ -17,27 +17,31 @@ linters:
|
|||||||
- typecheck
|
- typecheck
|
||||||
- unused
|
- unused
|
||||||
- varcheck
|
- varcheck
|
||||||
# Linters added by the stash project
|
# Linters added by the stash project.
|
||||||
# - bodyclose
|
|
||||||
- dogsled
|
- dogsled
|
||||||
- errorlint
|
- errorlint
|
||||||
# - exhaustive
|
# - exhaustive
|
||||||
- exportloopref
|
- exportloopref
|
||||||
# - gocritic
|
- gocritic
|
||||||
# - goerr113
|
# - goerr113
|
||||||
- gofmt
|
- gofmt
|
||||||
# - gomnd
|
# - gomnd
|
||||||
# - gosec
|
|
||||||
# - ifshort
|
# - ifshort
|
||||||
- misspell
|
- misspell
|
||||||
# - nakedret
|
# - nakedret
|
||||||
- noctx
|
- noctx
|
||||||
# - paralleltest
|
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
|
|
||||||
|
# Project-specific linter overrides
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
gocritic:
|
||||||
|
disabled-checks:
|
||||||
|
# Way too many errors to fix regarding comment formatting for now
|
||||||
|
- commentFormatting
|
||||||
|
- appendAssign
|
||||||
|
|
||||||
gofmt:
|
gofmt:
|
||||||
simplify: false
|
simplify: false
|
||||||
|
|
||||||
|
|||||||
@@ -175,10 +175,16 @@ type StashBoxFingerprint {
|
|||||||
duration: Int!
|
duration: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""If neither performer_ids nor performer_names are set, tag all performers"""
|
||||||
input StashBoxBatchPerformerTagInput {
|
input StashBoxBatchPerformerTagInput {
|
||||||
|
"Stash endpoint to use for the performer tagging"
|
||||||
endpoint: Int!
|
endpoint: Int!
|
||||||
|
"Fields to exclude when executing the performer tagging"
|
||||||
exclude_fields: [String!]
|
exclude_fields: [String!]
|
||||||
|
"Refresh performers already tagged by StashBox if true. Only tag performers with no StashBox tagging if false"
|
||||||
refresh: Boolean!
|
refresh: Boolean!
|
||||||
|
"If set, only tag these performer ids"
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
|
"If set, only tag these performer names"
|
||||||
performer_names: [String!]
|
performer_names: [String!]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -10,6 +11,11 @@ import (
|
|||||||
"github.com/stashapp/stash/pkg/plugin"
|
"github.com/stashapp/stash/pkg/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
ErrNotSupported = errors.New("not supported")
|
||||||
|
)
|
||||||
|
|
||||||
type hookExecutor interface {
|
type hookExecutor interface {
|
||||||
ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string)
|
ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*st
|
|||||||
|
|
||||||
// indicate that image is missing by setting default query param to true
|
// indicate that image is missing by setting default query param to true
|
||||||
if !hasImage {
|
if !hasImage {
|
||||||
imagePath = imagePath + "?default=true"
|
imagePath += "?default=true"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &imagePath, nil
|
return &imagePath, nil
|
||||||
|
|||||||
@@ -164,18 +164,19 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source models.Scr
|
|||||||
var singleScene *models.ScrapedScene
|
var singleScene *models.ScrapedScene
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if input.SceneID != nil {
|
switch {
|
||||||
|
case input.SceneID != nil:
|
||||||
var sceneID int
|
var sceneID int
|
||||||
sceneID, err = strconv.Atoi(*input.SceneID)
|
sceneID, err = strconv.Atoi(*input.SceneID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
singleScene, err = manager.GetInstance().ScraperCache.ScrapeScene(*source.ScraperID, sceneID)
|
singleScene, err = manager.GetInstance().ScraperCache.ScrapeScene(*source.ScraperID, sceneID)
|
||||||
} else if input.SceneInput != nil {
|
case input.SceneInput != nil:
|
||||||
singleScene, err = manager.GetInstance().ScraperCache.ScrapeSceneFragment(*source.ScraperID, *input.SceneInput)
|
singleScene, err = manager.GetInstance().ScraperCache.ScrapeSceneFragment(*source.ScraperID, *input.SceneInput)
|
||||||
} else if input.Query != nil {
|
case input.Query != nil:
|
||||||
return manager.GetInstance().ScraperCache.ScrapeSceneQuery(*source.ScraperID, *input.Query)
|
return manager.GetInstance().ScraperCache.ScrapeSceneQuery(*source.ScraperID, *input.Query)
|
||||||
} else {
|
default:
|
||||||
err = errors.New("scene_id, scene_input or query must be set")
|
err = errors.New("scene_id, scene_input or query must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +209,7 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source models.Scr
|
|||||||
|
|
||||||
func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiScenesInput) ([][]*models.ScrapedScene, error) {
|
func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiScenesInput) ([][]*models.ScrapedScene, error) {
|
||||||
if source.ScraperID != nil {
|
if source.ScraperID != nil {
|
||||||
return nil, errors.New("not implemented")
|
return nil, ErrNotImplemented
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -240,7 +241,7 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
|
|||||||
return manager.GetInstance().ScraperCache.ScrapePerformerList(*source.ScraperID, *input.Query)
|
return manager.GetInstance().ScraperCache.ScrapePerformerList(*source.ScraperID, *input.Query)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("not implemented")
|
return nil, ErrNotImplemented
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -248,12 +249,13 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ret []*models.StashBoxPerformerQueryResult
|
var ret []*models.StashBoxPerformerQueryResult
|
||||||
if input.PerformerID != nil {
|
switch {
|
||||||
|
case input.PerformerID != nil:
|
||||||
ret, err = client.FindStashBoxPerformersByNames([]string{*input.PerformerID})
|
ret, err = client.FindStashBoxPerformersByNames([]string{*input.PerformerID})
|
||||||
} else if input.Query != nil {
|
case input.Query != nil:
|
||||||
ret, err = client.QueryStashBoxPerformer(*input.Query)
|
ret, err = client.QueryStashBoxPerformer(*input.Query)
|
||||||
} else {
|
default:
|
||||||
return nil, errors.New("not implemented")
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -272,7 +274,7 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
|
|||||||
|
|
||||||
func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) {
|
func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) {
|
||||||
if source.ScraperID != nil {
|
if source.ScraperID != nil {
|
||||||
return nil, errors.New("not implemented")
|
return nil, ErrNotImplemented
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil {
|
||||||
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
client, err := r.getStashBoxClient(*source.StashBoxIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,17 +292,18 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source models.S
|
|||||||
var singleGallery *models.ScrapedGallery
|
var singleGallery *models.ScrapedGallery
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if input.GalleryID != nil {
|
switch {
|
||||||
|
case input.GalleryID != nil:
|
||||||
var galleryID int
|
var galleryID int
|
||||||
galleryID, err = strconv.Atoi(*input.GalleryID)
|
galleryID, err = strconv.Atoi(*input.GalleryID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
singleGallery, err = manager.GetInstance().ScraperCache.ScrapeGallery(*source.ScraperID, galleryID)
|
singleGallery, err = manager.GetInstance().ScraperCache.ScrapeGallery(*source.ScraperID, galleryID)
|
||||||
} else if input.GalleryInput != nil {
|
case input.GalleryInput != nil:
|
||||||
singleGallery, err = manager.GetInstance().ScraperCache.ScrapeGalleryFragment(*source.ScraperID, *input.GalleryInput)
|
singleGallery, err = manager.GetInstance().ScraperCache.ScrapeGalleryFragment(*source.ScraperID, *input.GalleryInput)
|
||||||
} else {
|
default:
|
||||||
return nil, errors.New("not implemented")
|
return nil, ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -313,12 +316,12 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source models.S
|
|||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if source.StashBoxIndex != nil {
|
} else if source.StashBoxIndex != nil {
|
||||||
return nil, errors.New("not supported")
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("scraper_id must be set")
|
return nil, errors.New("scraper_id must be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) {
|
func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) {
|
||||||
return nil, errors.New("not supported")
|
return nil, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getLogLevel(logType string) models.LogLevel {
|
func getLogLevel(logType string) models.LogLevel {
|
||||||
if logType == "progress" {
|
switch logType {
|
||||||
|
case "progress":
|
||||||
return models.LogLevelProgress
|
return models.LogLevelProgress
|
||||||
} else if logType == "debug" {
|
case "debug":
|
||||||
return models.LogLevelDebug
|
return models.LogLevelDebug
|
||||||
} else if logType == "info" {
|
case "info":
|
||||||
return models.LogLevelInfo
|
return models.LogLevelInfo
|
||||||
} else if logType == "warn" {
|
case "warn":
|
||||||
return models.LogLevelWarning
|
return models.LogLevelWarning
|
||||||
} else if logType == "error" {
|
case "error":
|
||||||
return models.LogLevelError
|
return models.LogLevelError
|
||||||
}
|
default:
|
||||||
|
|
||||||
// default to debug
|
|
||||||
return models.LogLevelDebug
|
return models.LogLevelDebug
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logEntriesFromLogItems(logItems []logger.LogItem) []*models.LogEntry {
|
func logEntriesFromLogItems(logItems []logger.LogItem) []*models.LogEntry {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func generateTestPaths(testName, ext string) (scenePatterns []string, falseScene
|
|||||||
// add test cases for intra-name separators
|
// add test cases for intra-name separators
|
||||||
for _, separator := range testSeparators {
|
for _, separator := range testSeparators {
|
||||||
if separator != " " {
|
if separator != " " {
|
||||||
scenePatterns = append(scenePatterns, generateNamePatterns(strings.Replace(testName, " ", separator, -1), separator, ext)...)
|
scenePatterns = append(scenePatterns, generateNamePatterns(strings.ReplaceAll(testName, " ", separator), separator, ext)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ func WithTxn(fn func(tx *sqlx.Tx) error) error {
|
|||||||
logger.Warnf("failure when performing transaction rollback: %v", err)
|
logger.Warnf("failure when performing transaction rollback: %v", err)
|
||||||
}
|
}
|
||||||
panic(p)
|
panic(p)
|
||||||
} else if err != nil {
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
// something went wrong, rollback
|
// something went wrong, rollback
|
||||||
if err := tx.Rollback(); err != nil {
|
if err := tx.Rollback(); err != nil {
|
||||||
logger.Warnf("failure when performing transaction rollback: %v", err)
|
logger.Warnf("failure when performing transaction rollback: %v", err)
|
||||||
|
|||||||
@@ -515,7 +515,8 @@ func (me *Server) contentDirectoryEventSubHandler(w http.ResponseWriter, r *http
|
|||||||
// the spec on eventing but hasn't been completed as I have nothing to
|
// the spec on eventing but hasn't been completed as I have nothing to
|
||||||
// test it with.
|
// test it with.
|
||||||
service := me.services["ContentDirectory"]
|
service := me.services["ContentDirectory"]
|
||||||
if r.Method == "SUBSCRIBE" && r.Header.Get("SID") == "" {
|
switch {
|
||||||
|
case r.Method == "SUBSCRIBE" && r.Header.Get("SID") == "":
|
||||||
urls := upnp.ParseCallbackURLs(r.Header.Get("CALLBACK"))
|
urls := upnp.ParseCallbackURLs(r.Header.Get("CALLBACK"))
|
||||||
var timeout int
|
var timeout int
|
||||||
fmt.Sscanf(r.Header.Get("TIMEOUT"), "Second-%d", &timeout)
|
fmt.Sscanf(r.Header.Get("TIMEOUT"), "Second-%d", &timeout)
|
||||||
@@ -528,9 +529,9 @@ func (me *Server) contentDirectoryEventSubHandler(w http.ResponseWriter, r *http
|
|||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
me.contentDirectoryInitialEvent(r.Context(), urls, sid)
|
me.contentDirectoryInitialEvent(r.Context(), urls, sid)
|
||||||
}()
|
}()
|
||||||
} else if r.Method == "SUBSCRIBE" {
|
case r.Method == "SUBSCRIBE":
|
||||||
http.Error(w, "meh", http.StatusPreconditionFailed)
|
http.Error(w, "meh", http.StatusPreconditionFailed)
|
||||||
} else {
|
default:
|
||||||
logger.Debugf("unhandled event method: %s", r.Method)
|
logger.Debugf("unhandled event method: %s", r.Method)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func (p *scenePager) getPages(r models.ReaderRepository, total int) ([]interface
|
|||||||
sceneTitle = sceneTitle[0:3]
|
sceneTitle = sceneTitle[0:3]
|
||||||
}
|
}
|
||||||
|
|
||||||
title = title + fmt.Sprintf(" (%s...)", sceneTitle)
|
title += fmt.Sprintf(" (%s...)", sceneTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
objs = append(objs, makeStorageFolder(p.getPageID(page), title, p.parentID))
|
objs = append(objs, makeStorageFolder(p.getPageID(page), title, p.parentID))
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func IsValidCodec(codecName string, supportedCodecs []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidAudio(audio AudioCodec, ValidCodecs []AudioCodec) bool {
|
func IsValidAudio(audio AudioCodec, validCodecs []AudioCodec) bool {
|
||||||
|
|
||||||
// if audio codec is missing or unsupported by ffmpeg we can't do anything about it
|
// if audio codec is missing or unsupported by ffmpeg we can't do anything about it
|
||||||
// report it as valid so that the file can at least be streamed directly if the video codec is supported
|
// report it as valid so that the file can at least be streamed directly if the video codec is supported
|
||||||
@@ -124,7 +124,7 @@ func IsValidAudio(audio AudioCodec, ValidCodecs []AudioCodec) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range ValidCodecs {
|
for _, c := range validCodecs {
|
||||||
if c == audio {
|
if c == audio {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrUnsupportedFormat = errors.New("unsupported image format")
|
||||||
|
|
||||||
func (e *Encoder) ImageThumbnail(image *bytes.Buffer, format *string, maxDimensions int, path string) ([]byte, error) {
|
func (e *Encoder) ImageThumbnail(image *bytes.Buffer, format *string, maxDimensions int, path string) ([]byte, error) {
|
||||||
// ffmpeg spends a long sniffing image format when data is piped through stdio, so we pass the format explicitly instead
|
// ffmpeg spends a long sniffing image format when data is piped through stdio, so we pass the format explicitly instead
|
||||||
ffmpegformat := ""
|
ffmpegformat := ""
|
||||||
if format != nil && *format == "jpeg" {
|
if format == nil {
|
||||||
|
return nil, ErrUnsupportedFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *format {
|
||||||
|
case "jpeg":
|
||||||
ffmpegformat = "mjpeg"
|
ffmpegformat = "mjpeg"
|
||||||
} else if format != nil && *format == "png" {
|
case "png":
|
||||||
ffmpegformat = "png_pipe"
|
ffmpegformat = "png_pipe"
|
||||||
} else if format != nil && *format == "webp" {
|
case "webp":
|
||||||
ffmpegformat = "webp_pipe"
|
ffmpegformat = "webp_pipe"
|
||||||
} else {
|
|
||||||
return nil, errors.New("unsupported image format")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func IsZipPath(p string) bool {
|
|||||||
// path separators within zip files. It returns the original provided path
|
// path separators within zip files. It returns the original provided path
|
||||||
// if it does not contain the zip file separator character.
|
// if it does not contain the zip file separator character.
|
||||||
func ZipPathDisplayName(path string) string {
|
func ZipPathDisplayName(path string) string {
|
||||||
return strings.Replace(path, zipSeparator, "/", -1)
|
return strings.ReplaceAll(path, zipSeparator, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZipFilePath(path string) (zipFilename, filename string) {
|
func ZipFilePath(path string) (zipFilename, filename string) {
|
||||||
|
|||||||
@@ -107,11 +107,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
gallery := s.input
|
gallery := s.input
|
||||||
json, err := ToBasicJSON(&gallery)
|
json, err := ToBasicJSON(&gallery)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,11 +163,12 @@ func TestGetStudioName(t *testing.T) {
|
|||||||
gallery := s.input
|
gallery := s.input
|
||||||
json, err := GetStudioName(mockStudioReader, &gallery)
|
json, err := GetStudioName(mockStudioReader, &gallery)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,9 +142,7 @@ func (scanner *Scanner) ScanNew(file file.SourceFile) (retGallery *models.Galler
|
|||||||
|
|
||||||
isUpdatedGallery = true
|
isUpdatedGallery = true
|
||||||
}
|
}
|
||||||
} else {
|
} else if scanner.hasImages(path) { // don't create gallery if it has no images
|
||||||
// don't create gallery if it has no images
|
|
||||||
if scanner.hasImages(path) {
|
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
g = &models.Gallery{
|
g = &models.Gallery{
|
||||||
@@ -174,7 +172,6 @@ func (scanner *Scanner) ScanNew(file file.SourceFile) (retGallery *models.Galler
|
|||||||
scanImages = true
|
scanImages = true
|
||||||
isNewGallery = true
|
isNewGallery = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
@@ -165,11 +165,12 @@ func TestGetStudioName(t *testing.T) {
|
|||||||
image := s.input
|
image := s.input
|
||||||
json, err := GetStudioName(mockStudioReader, &image)
|
json, err := GetStudioName(mockStudioReader, &image)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package image
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -13,6 +14,8 @@ import (
|
|||||||
var vipsPath string
|
var vipsPath string
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
|
|
||||||
|
var ErrUnsupportedFormat = errors.New("unsupported image format")
|
||||||
|
|
||||||
type ThumbnailEncoder struct {
|
type ThumbnailEncoder struct {
|
||||||
ffmpeg ffmpeg.Encoder
|
ffmpeg ffmpeg.Encoder
|
||||||
vips *vipsEncoder
|
vips *vipsEncoder
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ func (p *Progress) SetProcessed(processed int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Progress) calculatePercent() {
|
func (p *Progress) calculatePercent() {
|
||||||
if p.total <= 0 {
|
switch {
|
||||||
|
case p.total <= 0:
|
||||||
p.percent = ProgressIndefinite
|
p.percent = ProgressIndefinite
|
||||||
} else if p.processed < 0 {
|
case p.processed < 0:
|
||||||
p.percent = 0
|
p.percent = 0
|
||||||
} else {
|
default:
|
||||||
p.percent = float64(p.processed) / float64(p.total)
|
p.percent = float64(p.processed) / float64(p.total)
|
||||||
if p.percent > 1 {
|
if p.percent > 1 {
|
||||||
p.percent = 1
|
p.percent = 1
|
||||||
|
|||||||
@@ -80,13 +80,14 @@ func SetLogLevel(level string) {
|
|||||||
func logLevelFromString(level string) logrus.Level {
|
func logLevelFromString(level string) logrus.Level {
|
||||||
ret := logrus.InfoLevel
|
ret := logrus.InfoLevel
|
||||||
|
|
||||||
if level == "Debug" {
|
switch level {
|
||||||
|
case "Debug":
|
||||||
ret = logrus.DebugLevel
|
ret = logrus.DebugLevel
|
||||||
} else if level == "Warning" {
|
case "Warning":
|
||||||
ret = logrus.WarnLevel
|
ret = logrus.WarnLevel
|
||||||
} else if level == "Error" {
|
case "Error":
|
||||||
ret = logrus.ErrorLevel
|
ret = logrus.ErrorLevel
|
||||||
} else if level == "Trace" {
|
case "Trace":
|
||||||
ret = logrus.TraceLevel
|
ret = logrus.TraceLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,16 +160,14 @@ func (log *PluginLogger) HandleStderrLine(line string) {
|
|||||||
p, err := strconv.ParseFloat(ll, 64)
|
p, err := strconv.ParseFloat(ll, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Errorf("Error parsing progress value '%s': %s", ll, err.Error())
|
Errorf("Error parsing progress value '%s': %s", ll, err.Error())
|
||||||
} else {
|
} else if log.ProgressChan != nil { // only pass progress through if channel present
|
||||||
// only pass progress through if channel present
|
|
||||||
if log.ProgressChan != nil {
|
|
||||||
// don't block on this
|
// don't block on this
|
||||||
select {
|
select {
|
||||||
case log.ProgressChan <- p:
|
case log.ProgressChan <- p:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -175,6 +174,16 @@ func (e MissingConfigError) Error() string {
|
|||||||
return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", "))
|
return fmt.Sprintf("missing the following mandatory settings: %s", strings.Join(e.missingFields, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StashBoxError represents configuration errors of Stash-Box
|
||||||
|
type StashBoxError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StashBoxError) Error() string {
|
||||||
|
// "Stash-box" is a proper noun and is therefore capitcalized
|
||||||
|
return "Stash-box: " + s.msg
|
||||||
|
}
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
cpuProfilePath string
|
cpuProfilePath string
|
||||||
isNewSystem bool
|
isNewSystem bool
|
||||||
@@ -680,29 +689,30 @@ func (i *Instance) ValidateCredentials(username string, password string) bool {
|
|||||||
return username == authUser && err == nil
|
return username == authUser && err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stashBoxRe = regexp.MustCompile("^http.*graphql$")
|
||||||
|
|
||||||
func (i *Instance) ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
func (i *Instance) ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
||||||
isMulti := len(boxes) > 1
|
isMulti := len(boxes) > 1
|
||||||
|
|
||||||
re, err := regexp.Compile("^http.*graphql$")
|
for _, box := range boxes {
|
||||||
if err != nil {
|
// Validate each stash-box configuration field, return on error
|
||||||
return errors.New("failure to generate regular expression")
|
if box.APIKey == "" {
|
||||||
|
return &StashBoxError{msg: "API Key cannot be blank"}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, box := range boxes {
|
if box.Endpoint == "" {
|
||||||
if box.APIKey == "" {
|
return &StashBoxError{msg: "endpoint cannot be blank"}
|
||||||
//lint:ignore ST1005 Stash-box is a name
|
}
|
||||||
return errors.New("Stash-box API Key cannot be blank")
|
|
||||||
} else if box.Endpoint == "" {
|
if !stashBoxRe.Match([]byte(box.Endpoint)) {
|
||||||
//lint:ignore ST1005 Stash-box is a name
|
return &StashBoxError{msg: "endpoint is invalid"}
|
||||||
return errors.New("Stash-box Endpoint cannot be blank")
|
}
|
||||||
} else if !re.Match([]byte(box.Endpoint)) {
|
|
||||||
//lint:ignore ST1005 Stash-box is a name
|
if isMulti && box.Name == "" {
|
||||||
return errors.New("Stash-box Endpoint is invalid")
|
return &StashBoxError{msg: "name cannot be blank"}
|
||||||
} else if isMulti && box.Name == "" {
|
|
||||||
//lint:ignore ST1005 Stash-box is a name
|
|
||||||
return errors.New("Stash-box Name cannot be blank")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func (s *DownloadStore) RegisterFile(fp string, contentType string, keep bool) s
|
|||||||
for generate && a < attempts {
|
for generate && a < attempts {
|
||||||
hash = utils.GenerateRandomKey(keyLength)
|
hash = utils.GenerateRandomKey(keyLength)
|
||||||
_, generate = s.m[hash]
|
_, generate = s.m[hash]
|
||||||
a = a + 1
|
a++
|
||||||
}
|
}
|
||||||
|
|
||||||
s.m[hash] = &storeFile{
|
s.m[hash] = &storeFile{
|
||||||
|
|||||||
@@ -94,17 +94,15 @@ func Initialize() *singleton {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("error initializing configuration: %s", err.Error()))
|
panic(fmt.Sprintf("error initializing configuration: %s", err.Error()))
|
||||||
} else {
|
} else if err := instance.PostInit(ctx); err != nil {
|
||||||
if err := instance.PostInit(ctx); err != nil {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
initSecurity(cfg)
|
initSecurity(cfg)
|
||||||
} else {
|
} else {
|
||||||
cfgFile := cfg.GetConfigFile()
|
cfgFile := cfg.GetConfigFile()
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
cfgFile = cfgFile + " "
|
cfgFile += " "
|
||||||
}
|
}
|
||||||
|
|
||||||
// create temporary session store - this will be re-initialised
|
// create temporary session store - this will be re-initialised
|
||||||
|
|||||||
@@ -616,7 +616,13 @@ func (s *singleton) StashBoxBatchPerformerTag(ctx context.Context, input models.
|
|||||||
|
|
||||||
var tasks []StashBoxPerformerTagTask
|
var tasks []StashBoxPerformerTagTask
|
||||||
|
|
||||||
if len(input.PerformerIds) > 0 {
|
// The gocritic linter wants to turn this ifElseChain into a switch.
|
||||||
|
// however, such a switch would contain quite large blocks for each section
|
||||||
|
// and would arguably be hard to read.
|
||||||
|
//
|
||||||
|
// This is why we mark this section nolint. In principle, we should look to
|
||||||
|
// rewrite the section at some point, to avoid the linter warning.
|
||||||
|
if len(input.PerformerIds) > 0 { //nolint:gocritic
|
||||||
if err := s.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
if err := s.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||||
performerQuery := r.Performer()
|
performerQuery := r.Performer()
|
||||||
|
|
||||||
@@ -652,7 +658,11 @@ func (s *singleton) StashBoxBatchPerformerTag(ctx context.Context, input models.
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else { //nolint:gocritic
|
||||||
|
// The gocritic linter wants to fold this if-block into the else on the line above.
|
||||||
|
// However, this doesn't really help with readability of the current section. Mark it
|
||||||
|
// as nolint for now. In the future we'd like to rewrite this code by factoring some of
|
||||||
|
// this into separate functions.
|
||||||
if err := s.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
if err := s.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||||
performerQuery := r.Performer()
|
performerQuery := r.Performer()
|
||||||
var performers []*models.Performer
|
var performers []*models.Performer
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ func (t *autoTagFilesTask) makeSceneFilter() *models.SceneFilterType {
|
|||||||
|
|
||||||
for _, p := range t.paths {
|
for _, p := range t.paths {
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret.Path == nil {
|
if ret.Path == nil {
|
||||||
@@ -360,7 +360,7 @@ func (t *autoTagFilesTask) makeImageFilter() *models.ImageFilterType {
|
|||||||
|
|
||||||
for _, p := range t.paths {
|
for _, p := range t.paths {
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret.Path == nil {
|
if ret.Path == nil {
|
||||||
@@ -397,7 +397,7 @@ func (t *autoTagFilesTask) makeGalleryFilter() *models.GalleryFilterType {
|
|||||||
|
|
||||||
for _, p := range t.paths {
|
for _, p := range t.paths {
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
if ret.Path == nil {
|
if ret.Path == nil {
|
||||||
|
|||||||
@@ -209,17 +209,18 @@ func (j *ScanJob) doesPathExist(path string) bool {
|
|||||||
|
|
||||||
ret := false
|
ret := false
|
||||||
txnErr := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
txnErr := j.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||||
if utils.MatchExtension(path, gExt) {
|
switch {
|
||||||
gallery, _ := r.Gallery().FindByPath(path)
|
case utils.MatchExtension(path, gExt):
|
||||||
if gallery != nil {
|
g, _ := r.Gallery().FindByPath(path)
|
||||||
|
if g != nil {
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
} else if utils.MatchExtension(path, vidExt) {
|
case utils.MatchExtension(path, vidExt):
|
||||||
s, _ := r.Scene().FindByPath(path)
|
s, _ := r.Scene().FindByPath(path)
|
||||||
if s != nil {
|
if s != nil {
|
||||||
ret = true
|
ret = true
|
||||||
}
|
}
|
||||||
} else if utils.MatchExtension(path, imgExt) {
|
case utils.MatchExtension(path, imgExt):
|
||||||
i, _ := r.Image().FindByPath(path)
|
i, _ := r.Image().FindByPath(path)
|
||||||
if i != nil {
|
if i != nil {
|
||||||
ret = true
|
ret = true
|
||||||
@@ -259,16 +260,21 @@ func (t *ScanTask) Start(ctx context.Context) {
|
|||||||
var s *models.Scene
|
var s *models.Scene
|
||||||
path := t.file.Path()
|
path := t.file.Path()
|
||||||
t.progress.ExecuteTask("Scanning "+path, func() {
|
t.progress.ExecuteTask("Scanning "+path, func() {
|
||||||
if isGallery(path) {
|
switch {
|
||||||
|
case isGallery(path):
|
||||||
t.scanGallery(ctx)
|
t.scanGallery(ctx)
|
||||||
} else if isVideo(path) {
|
case isVideo(path):
|
||||||
s = t.scanScene()
|
s = t.scanScene()
|
||||||
} else if isImage(path) {
|
case isImage(path):
|
||||||
t.scanImage()
|
t.scanImage()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if s != nil {
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the case of a scene
|
||||||
iwg := sizedwaitgroup.New(2)
|
iwg := sizedwaitgroup.New(2)
|
||||||
|
|
||||||
if t.GenerateSprite {
|
if t.GenerateSprite {
|
||||||
@@ -332,7 +338,6 @@ func (t *ScanTask) Start(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iwg.Wait()
|
iwg.Wait()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func walkFilesToScan(s *models.StashConfig, f filepath.WalkFunc) error {
|
func walkFilesToScan(s *models.StashConfig, f filepath.WalkFunc) error {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func getPathQueryRegex(name string) string {
|
|||||||
// handle path separators
|
// handle path separators
|
||||||
const separator = `[` + separatorChars + `]`
|
const separator = `[` + separatorChars + `]`
|
||||||
|
|
||||||
ret := strings.Replace(name, " ", separator+"*", -1)
|
ret := strings.ReplaceAll(name, " ", separator+"*")
|
||||||
ret = `(?:^|_|[^\w\d])` + ret + `(?:$|_|[^\w\d])`
|
ret = `(?:^|_|[^\w\d])` + ret + `(?:$|_|[^\w\d])`
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func nameMatchesPath(name, path string) bool {
|
|||||||
// handle path separators
|
// handle path separators
|
||||||
const separator = `[` + separatorChars + `]`
|
const separator = `[` + separatorChars + `]`
|
||||||
|
|
||||||
reStr := strings.Replace(name, " ", separator+"*", -1)
|
reStr := strings.ReplaceAll(name, " ", separator+"*")
|
||||||
reStr = `(?:^|_|[^\w\d])` + reStr + `(?:$|_|[^\w\d])`
|
reStr = `(?:^|_|[^\w\d])` + reStr + `(?:$|_|[^\w\d])`
|
||||||
|
|
||||||
re := regexp.MustCompile(reStr)
|
re := regexp.MustCompile(reStr)
|
||||||
@@ -185,7 +185,7 @@ func scenePathsFilter(paths []string) *models.SceneFilterType {
|
|||||||
or = newOr
|
or = newOr
|
||||||
|
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
or.Path = &models.StringCriterionInput{
|
or.Path = &models.StringCriterionInput{
|
||||||
@@ -249,7 +249,7 @@ func imagePathsFilter(paths []string) *models.ImageFilterType {
|
|||||||
or = newOr
|
or = newOr
|
||||||
|
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
or.Path = &models.StringCriterionInput{
|
or.Path = &models.StringCriterionInput{
|
||||||
@@ -313,7 +313,7 @@ func galleryPathsFilter(paths []string) *models.GalleryFilterType {
|
|||||||
or = newOr
|
or = newOr
|
||||||
|
|
||||||
if !strings.HasSuffix(p, sep) {
|
if !strings.HasSuffix(p, sep) {
|
||||||
p = p + sep
|
p += sep
|
||||||
}
|
}
|
||||||
|
|
||||||
or.Path = &models.StringCriterionInput{
|
or.Path = &models.StringCriterionInput{
|
||||||
|
|||||||
@@ -13,16 +13,14 @@ type File struct {
|
|||||||
// GetHash returns the hash of the scene, based on the hash algorithm provided. If
|
// GetHash returns the hash of the scene, based on the hash algorithm provided. If
|
||||||
// hash algorithm is MD5, then Checksum is returned. Otherwise, OSHash is returned.
|
// hash algorithm is MD5, then Checksum is returned. Otherwise, OSHash is returned.
|
||||||
func (s File) GetHash(hashAlgorithm HashAlgorithm) string {
|
func (s File) GetHash(hashAlgorithm HashAlgorithm) string {
|
||||||
var ret string
|
switch hashAlgorithm {
|
||||||
if hashAlgorithm == HashAlgorithmMd5 {
|
case HashAlgorithmMd5:
|
||||||
ret = s.Checksum
|
return s.Checksum
|
||||||
} else if hashAlgorithm == HashAlgorithmOshash {
|
case HashAlgorithmOshash:
|
||||||
ret = s.OSHash
|
return s.OSHash
|
||||||
} else {
|
default:
|
||||||
panic("unknown hash algorithm")
|
panic("unknown hash algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s File) Equal(o File) bool {
|
func (s File) Equal(o File) bool {
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ func WithTxn(txn Transaction, fn func(r Repository) error) error {
|
|||||||
logger.Warnf("error while trying to roll back transaction: %v", err)
|
logger.Warnf("error while trying to roll back transaction: %v", err)
|
||||||
}
|
}
|
||||||
panic(p)
|
panic(p)
|
||||||
} else if err != nil {
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
// something went wrong, rollback
|
// something went wrong, rollback
|
||||||
if err := txn.Rollback(); err != nil {
|
if err := txn.Rollback(); err != nil {
|
||||||
logger.Warnf("error while trying to roll back transaction: %v", err)
|
logger.Warnf("error while trying to roll back transaction: %v", err)
|
||||||
@@ -66,7 +68,9 @@ func WithROTxn(txn ReadTransaction, fn func(r ReaderRepository) error) error {
|
|||||||
logger.Warnf("error while trying to roll back RO transaction: %v", err)
|
logger.Warnf("error while trying to roll back RO transaction: %v", err)
|
||||||
}
|
}
|
||||||
panic(p)
|
panic(p)
|
||||||
} else if err != nil {
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
// something went wrong, rollback
|
// something went wrong, rollback
|
||||||
if err := txn.Rollback(); err != nil {
|
if err := txn.Rollback(); err != nil {
|
||||||
logger.Warnf("error while trying to roll back RO transaction: %v", err)
|
logger.Warnf("error while trying to roll back RO transaction: %v", err)
|
||||||
|
|||||||
@@ -213,11 +213,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
movie := s.movie
|
movie := s.movie
|
||||||
json, err := ToJSON(mockMovieReader, mockStudioReader, &movie)
|
json, err := ToJSON(mockMovieReader, mockStudioReader, &movie)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,11 +201,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
tag := s.input
|
tag := s.input
|
||||||
json, err := ToJSON(mockPerformerReader, &tag)
|
json, err := ToJSON(mockPerformerReader, &tag)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func (c Config) getExecCommand(task *OperationConfig) []string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ret[i] = strings.Replace(arg, "{pluginDir}", dir, -1)
|
ret[i] = strings.ReplaceAll(arg, "{pluginDir}", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -226,11 +226,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
scene := s.input
|
scene := s.input
|
||||||
json, err := ToBasicJSON(mockSceneReader, &scene)
|
json, err := ToBasicJSON(mockSceneReader, &scene)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,11 +284,12 @@ func TestGetStudioName(t *testing.T) {
|
|||||||
scene := s.input
|
scene := s.input
|
||||||
json, err := GetStudioName(mockStudioReader, &scene)
|
json, err := GetStudioName(mockStudioReader, &scene)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,11 +345,12 @@ func TestGetTagNames(t *testing.T) {
|
|||||||
scene := s.input
|
scene := s.input
|
||||||
json, err := GetTagNames(mockTagReader, &scene)
|
json, err := GetTagNames(mockTagReader, &scene)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -435,11 +438,12 @@ func TestGetSceneMoviesJSON(t *testing.T) {
|
|||||||
scene := s.input
|
scene := s.input
|
||||||
json, err := GetSceneMoviesJSON(mockMovieReader, mockSceneReader, &scene)
|
json, err := GetSceneMoviesJSON(mockMovieReader, mockSceneReader, &scene)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,11 +621,12 @@ func TestGetSceneMarkersJSON(t *testing.T) {
|
|||||||
scene := s.input
|
scene := s.input
|
||||||
json, err := GetSceneMarkersJSON(mockMarkerReader, mockTagReader, &scene)
|
json, err := GetSceneMarkersJSON(mockMarkerReader, mockTagReader, &scene)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,11 +396,13 @@ func (i *Importer) Name() string {
|
|||||||
func (i *Importer) FindExistingID() (*int, error) {
|
func (i *Importer) FindExistingID() (*int, error) {
|
||||||
var existing *models.Scene
|
var existing *models.Scene
|
||||||
var err error
|
var err error
|
||||||
if i.FileNamingAlgorithm == models.HashAlgorithmMd5 {
|
|
||||||
|
switch i.FileNamingAlgorithm {
|
||||||
|
case models.HashAlgorithmMd5:
|
||||||
existing, err = i.ReaderWriter.FindByChecksum(i.Input.Checksum)
|
existing, err = i.ReaderWriter.FindByChecksum(i.Input.Checksum)
|
||||||
} else if i.FileNamingAlgorithm == models.HashAlgorithmOshash {
|
case models.HashAlgorithmOshash:
|
||||||
existing, err = i.ReaderWriter.FindByOSHash(i.Input.OSHash)
|
existing, err = i.ReaderWriter.FindByOSHash(i.Input.OSHash)
|
||||||
} else {
|
default:
|
||||||
panic("unknown file naming algorithm")
|
panic("unknown file naming algorithm")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (c *autotagSceneScraper) scrapeByScene(scene *models.Scene) (*models.Scrape
|
|||||||
var ret *models.ScrapedScene
|
var ret *models.ScrapedScene
|
||||||
|
|
||||||
// populate performers, studio and tags based on scene path
|
// populate performers, studio and tags based on scene path
|
||||||
if err := c.txnManager.WithReadTxn(context.Background(), func(r models.ReaderRepository) error {
|
if err := c.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||||
path := scene.Path
|
path := scene.Path
|
||||||
performers, err := c.matchPerformers(path, r.Performer())
|
performers, err := c.matchPerformers(path, r.Performer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,7 +150,7 @@ func (c *autotagGalleryScraper) scrapeByGallery(gallery *models.Gallery) (*model
|
|||||||
var ret *models.ScrapedGallery
|
var ret *models.ScrapedGallery
|
||||||
|
|
||||||
// populate performers, studio and tags based on scene path
|
// populate performers, studio and tags based on scene path
|
||||||
if err := c.txnManager.WithReadTxn(context.Background(), func(r models.ReaderRepository) error {
|
if err := c.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||||
path := gallery.Path.String
|
path := gallery.Path.String
|
||||||
performers, err := c.matchPerformers(path, r.Performer())
|
performers, err := c.matchPerformers(path, r.Performer())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ func (s *jsonScraper) scrapePerformersByName(name string) ([]*models.ScrapedPerf
|
|||||||
escapedName := url.QueryEscape(name)
|
escapedName := url.QueryEscape(name)
|
||||||
|
|
||||||
url := s.scraper.QueryURL
|
url := s.scraper.QueryURL
|
||||||
url = strings.Replace(url, placeholder, escapedName, -1)
|
url = strings.ReplaceAll(url, placeholder, escapedName)
|
||||||
|
|
||||||
doc, err := s.loadURL(context.TODO(), url)
|
doc, err := s.loadURL(context.TODO(), url)
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ func (s *jsonScraper) scrapeScenesByName(name string) ([]*models.ScrapedScene, e
|
|||||||
escapedName := url.QueryEscape(name)
|
escapedName := url.QueryEscape(name)
|
||||||
|
|
||||||
url := s.scraper.QueryURL
|
url := s.scraper.QueryURL
|
||||||
url = strings.Replace(url, placeholder, escapedName, -1)
|
url = strings.ReplaceAll(url, placeholder, escapedName)
|
||||||
|
|
||||||
doc, err := s.loadURL(context.TODO(), url)
|
doc, err := s.loadURL(context.TODO(), url)
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (s mappedConfig) applyCommon(c commonMappedConfig, src string) string {
|
|||||||
|
|
||||||
ret := src
|
ret := src
|
||||||
for commonKey, commonVal := range c {
|
for commonKey, commonVal := range c {
|
||||||
ret = strings.Replace(ret, commonKey, commonVal, -1)
|
ret = strings.ReplaceAll(ret, commonKey, commonVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -486,7 +486,7 @@ func (p *postProcessLbToKg) Apply(value string, q mappedQuery) string {
|
|||||||
const lb_in_kg = 0.45359237
|
const lb_in_kg = 0.45359237
|
||||||
w, err := strconv.ParseFloat(value, 64)
|
w, err := strconv.ParseFloat(value, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w = w * lb_in_kg
|
w *= lb_in_kg
|
||||||
value = strconv.Itoa(int(math.Round(w)))
|
value = strconv.Itoa(int(math.Round(w)))
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
@@ -576,7 +576,7 @@ type mappedScraperAttrConfig struct {
|
|||||||
|
|
||||||
postProcessActions []postProcessAction
|
postProcessActions []postProcessAction
|
||||||
|
|
||||||
// deprecated: use PostProcess instead
|
// Deprecated: use PostProcess instead
|
||||||
ParseDate string `yaml:"parseDate"`
|
ParseDate string `yaml:"parseDate"`
|
||||||
Replace mappedRegexConfigs `yaml:"replace"`
|
Replace mappedRegexConfigs `yaml:"replace"`
|
||||||
SubScraper *mappedScraperAttrConfig `yaml:"subScraper"`
|
SubScraper *mappedScraperAttrConfig `yaml:"subScraper"`
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (p queryURLParameters) applyReplacements(r queryURLReplacements) {
|
|||||||
func (p queryURLParameters) constructURL(url string) string {
|
func (p queryURLParameters) constructURL(url string) string {
|
||||||
ret := url
|
ret := url
|
||||||
for k, v := range p {
|
for k, v := range p {
|
||||||
ret = strings.Replace(ret, "{"+k+"}", v, -1)
|
ret = strings.ReplaceAll(ret, "{"+k+"}", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@@ -479,11 +479,12 @@ func formatCareerLength(start, end *int) *string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ret string
|
var ret string
|
||||||
if end == nil {
|
switch {
|
||||||
|
case end == nil:
|
||||||
ret = fmt.Sprintf("%d -", *start)
|
ret = fmt.Sprintf("%d -", *start)
|
||||||
} else if start == nil {
|
case start == nil:
|
||||||
ret = fmt.Sprintf("- %d", *end)
|
ret = fmt.Sprintf("- %d", *end)
|
||||||
} else {
|
default:
|
||||||
ret = fmt.Sprintf("%d - %d", *start, *end)
|
ret = fmt.Sprintf("%d - %d", *start, *end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (s *xpathScraper) scrapePerformersByName(name string) ([]*models.ScrapedPer
|
|||||||
escapedName := url.QueryEscape(name)
|
escapedName := url.QueryEscape(name)
|
||||||
|
|
||||||
url := s.scraper.QueryURL
|
url := s.scraper.QueryURL
|
||||||
url = strings.Replace(url, placeholder, escapedName, -1)
|
url = strings.ReplaceAll(url, placeholder, escapedName)
|
||||||
|
|
||||||
doc, err := s.loadURL(context.TODO(), url)
|
doc, err := s.loadURL(context.TODO(), url)
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ func (s *xpathScraper) scrapeScenesByName(name string) ([]*models.ScrapedScene,
|
|||||||
escapedName := url.QueryEscape(name)
|
escapedName := url.QueryEscape(name)
|
||||||
|
|
||||||
url := s.scraper.QueryURL
|
url := s.scraper.QueryURL
|
||||||
url = strings.Replace(url, placeholder, escapedName, -1)
|
url = strings.ReplaceAll(url, placeholder, escapedName)
|
||||||
|
|
||||||
doc, err := s.loadURL(context.TODO(), url)
|
doc, err := s.loadURL(context.TODO(), url)
|
||||||
|
|
||||||
|
|||||||
@@ -70,12 +70,10 @@ func CheckAllowPublicWithoutAuth(c *config.Instance, r *http.Request) error {
|
|||||||
return UntrustedProxyError(requestIP)
|
return UntrustedProxyError(requestIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if !isLocalIP(requestIP) { // request was not proxied
|
||||||
// request was not proxied
|
|
||||||
if !isLocalIP(requestIP) {
|
|
||||||
return ExternalAccessError(requestIP)
|
return ExternalAccessError(requestIP)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -210,11 +210,9 @@ func (f *filterBuilder) getSubFilterClause(clause, subFilterClause string) strin
|
|||||||
var op string
|
var op string
|
||||||
if len(ret) > 0 {
|
if len(ret) > 0 {
|
||||||
op = " " + f.subFilterOp + " "
|
op = " " + f.subFilterOp + " "
|
||||||
} else {
|
} else if f.subFilterOp == notOp {
|
||||||
if f.subFilterOp == notOp {
|
|
||||||
op = "NOT "
|
op = "NOT "
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ret += op + "(" + subFilterClause + ")"
|
ret += op + "(" + subFilterClause + ")"
|
||||||
}
|
}
|
||||||
@@ -436,16 +434,18 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(criterion *models.MultiCrit
|
|||||||
|
|
||||||
whereClause := ""
|
whereClause := ""
|
||||||
havingClause := ""
|
havingClause := ""
|
||||||
if criterion.Modifier == models.CriterionModifierIncludes {
|
|
||||||
|
switch criterion.Modifier {
|
||||||
|
case models.CriterionModifierIncludes:
|
||||||
// includes any of the provided ids
|
// includes any of the provided ids
|
||||||
m.addJoinTable(f)
|
m.addJoinTable(f)
|
||||||
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
||||||
} else if criterion.Modifier == models.CriterionModifierIncludesAll {
|
case models.CriterionModifierIncludesAll:
|
||||||
// includes all of the provided ids
|
// includes all of the provided ids
|
||||||
m.addJoinTable(f)
|
m.addJoinTable(f)
|
||||||
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
||||||
havingClause = fmt.Sprintf("count(distinct %s.%s) IS %d", joinAlias, m.foreignFK, len(criterion.Value))
|
havingClause = fmt.Sprintf("count(distinct %s.%s) IS %d", joinAlias, m.foreignFK, len(criterion.Value))
|
||||||
} else if criterion.Modifier == models.CriterionModifierExcludes {
|
case models.CriterionModifierExcludes:
|
||||||
// excludes all of the provided ids
|
// excludes all of the provided ids
|
||||||
// need to use actual join table name for this
|
// need to use actual join table name for this
|
||||||
// <primaryTable>.id NOT IN (select <joinTable>.<primaryFK> from <joinTable> where <joinTable>.<foreignFK> in <values>)
|
// <primaryTable>.id NOT IN (select <joinTable>.<primaryFK> from <joinTable> where <joinTable>.<foreignFK> in <values>)
|
||||||
@@ -620,12 +620,13 @@ WHERE id in {inBinding}
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addHierarchicalConditionClauses(f *filterBuilder, criterion *models.HierarchicalMultiCriterionInput, table, idColumn string) {
|
func addHierarchicalConditionClauses(f *filterBuilder, criterion *models.HierarchicalMultiCriterionInput, table, idColumn string) {
|
||||||
if criterion.Modifier == models.CriterionModifierIncludes {
|
switch criterion.Modifier {
|
||||||
|
case models.CriterionModifierIncludes:
|
||||||
f.addWhere(fmt.Sprintf("%s.%s IS NOT NULL", table, idColumn))
|
f.addWhere(fmt.Sprintf("%s.%s IS NOT NULL", table, idColumn))
|
||||||
} else if criterion.Modifier == models.CriterionModifierIncludesAll {
|
case models.CriterionModifierIncludesAll:
|
||||||
f.addWhere(fmt.Sprintf("%s.%s IS NOT NULL", table, idColumn))
|
f.addWhere(fmt.Sprintf("%s.%s IS NOT NULL", table, idColumn))
|
||||||
f.addHaving(fmt.Sprintf("count(distinct %s.%s) IS %d", table, idColumn, len(criterion.Value)))
|
f.addHaving(fmt.Sprintf("count(distinct %s.%s) IS %d", table, idColumn, len(criterion.Value)))
|
||||||
} else if criterion.Modifier == models.CriterionModifierExcludes {
|
case models.CriterionModifierExcludes:
|
||||||
f.addWhere(fmt.Sprintf("%s.%s IS NULL", table, idColumn))
|
f.addWhere(fmt.Sprintf("%s.%s IS NULL", table, idColumn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,13 +418,14 @@ func galleryAverageResolutionCriterionHandler(qb *galleryQueryBuilder, resolutio
|
|||||||
|
|
||||||
const widthHeight = "avg(MIN(images.width, images.height))"
|
const widthHeight = "avg(MIN(images.width, images.height))"
|
||||||
|
|
||||||
if resolution.Modifier == models.CriterionModifierEquals {
|
switch resolution.Modifier {
|
||||||
|
case models.CriterionModifierEquals:
|
||||||
f.addHaving(fmt.Sprintf("%s BETWEEN %d AND %d", widthHeight, min, max))
|
f.addHaving(fmt.Sprintf("%s BETWEEN %d AND %d", widthHeight, min, max))
|
||||||
} else if resolution.Modifier == models.CriterionModifierNotEquals {
|
case models.CriterionModifierNotEquals:
|
||||||
f.addHaving(fmt.Sprintf("%s NOT BETWEEN %d AND %d", widthHeight, min, max))
|
f.addHaving(fmt.Sprintf("%s NOT BETWEEN %d AND %d", widthHeight, min, max))
|
||||||
} else if resolution.Modifier == models.CriterionModifierLessThan {
|
case models.CriterionModifierLessThan:
|
||||||
f.addHaving(fmt.Sprintf("%s < %d", widthHeight, min))
|
f.addHaving(fmt.Sprintf("%s < %d", widthHeight, min))
|
||||||
} else if resolution.Modifier == models.CriterionModifierGreaterThan {
|
case models.CriterionModifierGreaterThan:
|
||||||
f.addHaving(fmt.Sprintf("%s > %d", widthHeight, max))
|
f.addHaving(fmt.Sprintf("%s > %d", widthHeight, max))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,12 +225,13 @@ func moviePerformersCriterionHandler(qb *movieQueryBuilder, performers *models.M
|
|||||||
)`, args...)
|
)`, args...)
|
||||||
f.addJoin("movies_performers", "", "movies.id = movies_performers.movie_id")
|
f.addJoin("movies_performers", "", "movies.id = movies_performers.movie_id")
|
||||||
|
|
||||||
if performers.Modifier == models.CriterionModifierIncludes {
|
switch performers.Modifier {
|
||||||
|
case models.CriterionModifierIncludes:
|
||||||
f.addWhere("movies_performers.performer_id IS NOT NULL")
|
f.addWhere("movies_performers.performer_id IS NOT NULL")
|
||||||
} else if performers.Modifier == models.CriterionModifierIncludesAll {
|
case models.CriterionModifierIncludesAll:
|
||||||
f.addWhere("movies_performers.performer_id IS NOT NULL")
|
f.addWhere("movies_performers.performer_id IS NOT NULL")
|
||||||
f.addHaving("COUNT(DISTINCT movies_performers.performer_id) = ?", len(performers.Value))
|
f.addHaving("COUNT(DISTINCT movies_performers.performer_id) = ?", len(performers.Value))
|
||||||
} else if performers.Modifier == models.CriterionModifierExcludes {
|
case models.CriterionModifierExcludes:
|
||||||
f.addWhere("movies_performers.performer_id IS NULL")
|
f.addWhere("movies_performers.performer_id IS NULL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -443,13 +443,14 @@ func performerStudiosCriterionHandler(qb *performerQueryBuilder, studios *models
|
|||||||
if studios != nil {
|
if studios != nil {
|
||||||
var clauseCondition string
|
var clauseCondition string
|
||||||
|
|
||||||
if studios.Modifier == models.CriterionModifierIncludes {
|
switch studios.Modifier {
|
||||||
|
case models.CriterionModifierIncludes:
|
||||||
// return performers who appear in scenes/images/galleries with any of the given studios
|
// return performers who appear in scenes/images/galleries with any of the given studios
|
||||||
clauseCondition = "NOT"
|
clauseCondition = "NOT"
|
||||||
} else if studios.Modifier == models.CriterionModifierExcludes {
|
case models.CriterionModifierExcludes:
|
||||||
// exclude performers who appear in scenes/images/galleries with any of the given studios
|
// exclude performers who appear in scenes/images/galleries with any of the given studios
|
||||||
clauseCondition = ""
|
clauseCondition = ""
|
||||||
} else {
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -482,13 +482,14 @@ func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, hei
|
|||||||
|
|
||||||
widthHeight := fmt.Sprintf("MIN(%s, %s)", widthColumn, heightColumn)
|
widthHeight := fmt.Sprintf("MIN(%s, %s)", widthColumn, heightColumn)
|
||||||
|
|
||||||
if resolution.Modifier == models.CriterionModifierEquals {
|
switch resolution.Modifier {
|
||||||
|
case models.CriterionModifierEquals:
|
||||||
f.addWhere(fmt.Sprintf("%s BETWEEN %d AND %d", widthHeight, min, max))
|
f.addWhere(fmt.Sprintf("%s BETWEEN %d AND %d", widthHeight, min, max))
|
||||||
} else if resolution.Modifier == models.CriterionModifierNotEquals {
|
case models.CriterionModifierNotEquals:
|
||||||
f.addWhere(fmt.Sprintf("%s NOT BETWEEN %d AND %d", widthHeight, min, max))
|
f.addWhere(fmt.Sprintf("%s NOT BETWEEN %d AND %d", widthHeight, min, max))
|
||||||
} else if resolution.Modifier == models.CriterionModifierLessThan {
|
case models.CriterionModifierLessThan:
|
||||||
f.addWhere(fmt.Sprintf("%s < %d", widthHeight, min))
|
f.addWhere(fmt.Sprintf("%s < %d", widthHeight, min))
|
||||||
} else if resolution.Modifier == models.CriterionModifierGreaterThan {
|
case models.CriterionModifierGreaterThan:
|
||||||
f.addWhere(fmt.Sprintf("%s > %d", widthHeight, max))
|
f.addWhere(fmt.Sprintf("%s > %d", widthHeight, max))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,13 +106,13 @@ func (qb *sceneMarkerQueryBuilder) CountByTagID(tagID int) (int, error) {
|
|||||||
func (qb *sceneMarkerQueryBuilder) GetMarkerStrings(q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
|
func (qb *sceneMarkerQueryBuilder) GetMarkerStrings(q *string, sort *string) ([]*models.MarkerStringsResultType, error) {
|
||||||
query := "SELECT count(*) as `count`, scene_markers.id as id, scene_markers.title as title FROM scene_markers"
|
query := "SELECT count(*) as `count`, scene_markers.id as id, scene_markers.title as title FROM scene_markers"
|
||||||
if q != nil {
|
if q != nil {
|
||||||
query = query + " WHERE title LIKE '%" + *q + "%'"
|
query += " WHERE title LIKE '%" + *q + "%'"
|
||||||
}
|
}
|
||||||
query = query + " GROUP BY title"
|
query += " GROUP BY title"
|
||||||
if sort != nil && *sort == "count" {
|
if sort != nil && *sort == "count" {
|
||||||
query = query + " ORDER BY `count` DESC"
|
query += " ORDER BY `count` DESC"
|
||||||
} else {
|
} else {
|
||||||
query = query + " ORDER BY title ASC"
|
query += " ORDER BY title ASC"
|
||||||
}
|
}
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
return qb.queryMarkerStringsResultType(query, args)
|
return qb.queryMarkerStringsResultType(query, args)
|
||||||
|
|||||||
@@ -58,14 +58,15 @@ func getSort(sort string, direction string, tableName string) string {
|
|||||||
|
|
||||||
const randomSeedPrefix = "random_"
|
const randomSeedPrefix = "random_"
|
||||||
|
|
||||||
if strings.HasSuffix(sort, "_count") {
|
switch {
|
||||||
|
case strings.HasSuffix(sort, "_count"):
|
||||||
var relationTableName = strings.TrimSuffix(sort, "_count") // TODO: pluralize?
|
var relationTableName = strings.TrimSuffix(sort, "_count") // TODO: pluralize?
|
||||||
colName := getColumn(relationTableName, "id")
|
colName := getColumn(relationTableName, "id")
|
||||||
return " ORDER BY COUNT(distinct " + colName + ") " + direction
|
return " ORDER BY COUNT(distinct " + colName + ") " + direction
|
||||||
} else if strings.Compare(sort, "filesize") == 0 {
|
case strings.Compare(sort, "filesize") == 0:
|
||||||
colName := getColumn(tableName, "size")
|
colName := getColumn(tableName, "size")
|
||||||
return " ORDER BY cast(" + colName + " as integer) " + direction
|
return " ORDER BY cast(" + colName + " as integer) " + direction
|
||||||
} else if strings.HasPrefix(sort, randomSeedPrefix) {
|
case strings.HasPrefix(sort, randomSeedPrefix):
|
||||||
// seed as a parameter from the UI
|
// seed as a parameter from the UI
|
||||||
// turn the provided seed into a float
|
// turn the provided seed into a float
|
||||||
seedStr := "0." + sort[len(randomSeedPrefix):]
|
seedStr := "0." + sort[len(randomSeedPrefix):]
|
||||||
@@ -75,9 +76,9 @@ func getSort(sort string, direction string, tableName string) string {
|
|||||||
seed = randomSortFloat
|
seed = randomSortFloat
|
||||||
}
|
}
|
||||||
return getRandomSort(tableName, direction, seed)
|
return getRandomSort(tableName, direction, seed)
|
||||||
} else if strings.Compare(sort, "random") == 0 {
|
case strings.Compare(sort, "random") == 0:
|
||||||
return getRandomSort(tableName, direction, randomSortFloat)
|
return getRandomSort(tableName, direction, randomSortFloat)
|
||||||
} else {
|
default:
|
||||||
colName := getColumn(tableName, sort)
|
colName := getColumn(tableName, sort)
|
||||||
var additional string
|
var additional string
|
||||||
if tableName == "scenes" {
|
if tableName == "scenes" {
|
||||||
@@ -202,14 +203,15 @@ func getIntCriterionWhereClause(column string, input models.IntCriterionInput) (
|
|||||||
func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, foreignFK string, criterion *models.MultiCriterionInput) (string, string) {
|
func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, foreignFK string, criterion *models.MultiCriterionInput) (string, string) {
|
||||||
whereClause := ""
|
whereClause := ""
|
||||||
havingClause := ""
|
havingClause := ""
|
||||||
if criterion.Modifier == models.CriterionModifierIncludes {
|
switch criterion.Modifier {
|
||||||
|
case models.CriterionModifierIncludes:
|
||||||
// includes any of the provided ids
|
// includes any of the provided ids
|
||||||
whereClause = foreignTable + ".id IN " + getInBinding(len(criterion.Value))
|
whereClause = foreignTable + ".id IN " + getInBinding(len(criterion.Value))
|
||||||
} else if criterion.Modifier == models.CriterionModifierIncludesAll {
|
case models.CriterionModifierIncludesAll:
|
||||||
// includes all of the provided ids
|
// includes all of the provided ids
|
||||||
whereClause = foreignTable + ".id IN " + getInBinding(len(criterion.Value))
|
whereClause = foreignTable + ".id IN " + getInBinding(len(criterion.Value))
|
||||||
havingClause = "count(distinct " + foreignTable + ".id) IS " + strconv.Itoa(len(criterion.Value))
|
havingClause = "count(distinct " + foreignTable + ".id) IS " + strconv.Itoa(len(criterion.Value))
|
||||||
} else if criterion.Modifier == models.CriterionModifierExcludes {
|
case models.CriterionModifierExcludes:
|
||||||
// excludes all of the provided ids
|
// excludes all of the provided ids
|
||||||
if joinTable != "" {
|
if joinTable != "" {
|
||||||
whereClause = primaryTable + ".id not in (select " + joinTable + "." + primaryFK + " from " + joinTable + " where " + joinTable + "." + foreignFK + " in " + getInBinding(len(criterion.Value)) + ")"
|
whereClause = primaryTable + ".id not in (select " + joinTable + "." + primaryFK + " from " + joinTable + " where " + joinTable + "." + foreignFK + " in " + getInBinding(len(criterion.Value)) + ")"
|
||||||
|
|||||||
@@ -184,11 +184,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
studio := s.input
|
studio := s.input
|
||||||
json, err := ToJSON(mockStudioReader, &studio)
|
json, err := ToJSON(mockStudioReader, &studio)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,11 +130,12 @@ func TestToJSON(t *testing.T) {
|
|||||||
tag := s.tag
|
tag := s.tag
|
||||||
json, err := ToJSON(mockTagReader, &tag)
|
json, err := ToJSON(mockTagReader, &tag)
|
||||||
|
|
||||||
if !s.err && err != nil {
|
switch {
|
||||||
|
case !s.err && err != nil:
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
||||||
} else if s.err && err == nil {
|
case s.err && err == nil:
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
t.Errorf("[%d] expected error not returned", i)
|
||||||
} else {
|
default:
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
assert.Equal(t, s.expected, json, "[%d]", i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
// FixWindowsPath replaces \ with / in the given path because sometimes the \ isn't recognized as valid on windows
|
// FixWindowsPath replaces \ with / in the given path because sometimes the \ isn't recognized as valid on windows
|
||||||
func FixWindowsPath(str string) string {
|
func FixWindowsPath(str string) string {
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return strings.Replace(str, "\\", "/", -1)
|
return strings.ReplaceAll(str, "\\", "/")
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user