mirror of
https://github.com/stashapp/stash.git
synced 2026-05-09 05:05:29 +02:00
Merge branch 'develop' into fix/scraper-scene-nil-pointer
This commit is contained in:
commit
17e599dff4
71 changed files with 2381 additions and 2776 deletions
2
.github/workflows/build-compiler.yml
vendored
2
.github/workflows/build-compiler.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: ghcr.io/stashapp/compiler:13
|
||||
COMPILER_IMAGE: ghcr.io/stashapp/compiler:14
|
||||
|
||||
jobs:
|
||||
build-compiler:
|
||||
|
|
|
|||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -15,7 +15,7 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COMPILER_IMAGE: ghcr.io/stashapp/compiler:13
|
||||
COMPILER_IMAGE: ghcr.io/stashapp/compiler:14
|
||||
|
||||
jobs:
|
||||
# Job 1: Generate code and build UI
|
||||
|
|
@ -30,6 +30,8 @@ jobs:
|
|||
fetch-tags: true
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
# pnpm version is read from the packageManager field in package.json
|
||||
# very broken (4.3, 4.4)
|
||||
|
|
|
|||
6
.github/workflows/golangci-lint.yml
vendored
6
.github/workflows/golangci-lint.yml
vendored
|
|
@ -17,6 +17,8 @@ jobs:
|
|||
# no tags or depth needed for lint
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
# generate-backend runs natively (just go generate + touch-ui) — no Docker needed
|
||||
- name: Generate Backend
|
||||
|
|
@ -25,4 +27,6 @@ jobs:
|
|||
## WARN
|
||||
## using v1, update in a later PR
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v2.11.4
|
||||
165
.golangci.yml
165
.golangci.yml
|
|
@ -1,87 +1,100 @@
|
|||
# options for analysis running
|
||||
run:
|
||||
timeout: 5m
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
# Default set of linters from golangci-lint
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
# Linters added by the stash project.
|
||||
# - contextcheck
|
||||
- copyloopvar
|
||||
- dogsled
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errorlint
|
||||
# - exhaustive
|
||||
- gocritic
|
||||
# - goerr113
|
||||
- gofmt
|
||||
# - gomnd
|
||||
# - ifshort
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
# - nakedret
|
||||
- noctx
|
||||
|
||||
# TODO - fix these in a later PR
|
||||
# - noctx
|
||||
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
|
||||
# Project-specific linter overrides
|
||||
linters-settings:
|
||||
gofmt:
|
||||
simplify: false
|
||||
|
||||
errorlint:
|
||||
# Disable errorf because there are false positives, where you don't want to wrap
|
||||
# an error.
|
||||
errorf: false
|
||||
asserts: true
|
||||
comparison: true
|
||||
|
||||
revive:
|
||||
ignore-generated-header: true
|
||||
severity: error
|
||||
confidence: 0.8
|
||||
rules:
|
||||
- name: blank-imports
|
||||
disabled: true
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
disabled: true
|
||||
- name: if-return
|
||||
disabled: true
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
disabled: true
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: indent-error-flow
|
||||
disabled: true
|
||||
- name: errorf
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: superfluous-else
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
|
||||
rowserrcheck:
|
||||
packages:
|
||||
- github.com/jmoiron/sqlx
|
||||
- staticcheck
|
||||
- unused
|
||||
|
||||
settings:
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
|
||||
# we specify (unnecessary) embedded fields for clarity in many places
|
||||
- -QF1008
|
||||
|
||||
# there's lots of misnamed (eg intId instead of intID) fields in the code.
|
||||
# it's not exactly world-ending, so I'm deferring fixing these for now
|
||||
- -ST1003
|
||||
errorlint:
|
||||
errorf: false
|
||||
asserts: true
|
||||
comparison: true
|
||||
revive:
|
||||
confidence: 0.8
|
||||
severity: error
|
||||
rules:
|
||||
- name: blank-imports
|
||||
disabled: true
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: dot-imports
|
||||
- name: error-return
|
||||
- name: error-strings
|
||||
- name: error-naming
|
||||
- name: exported
|
||||
disabled: true
|
||||
- name: if-return
|
||||
disabled: true
|
||||
- name: increment-decrement
|
||||
- name: var-naming
|
||||
disabled: true
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: receiver-naming
|
||||
- name: time-naming
|
||||
- name: unexported-return
|
||||
disabled: true
|
||||
- name: indent-error-flow
|
||||
disabled: true
|
||||
- name: errorf
|
||||
- name: empty-block
|
||||
disabled: true
|
||||
- name: superfluous-else
|
||||
- name: unused-parameter
|
||||
disabled: true
|
||||
- name: unreachable-code
|
||||
- name: redefines-builtin-id
|
||||
rowserrcheck:
|
||||
packages:
|
||||
- github.com/jmoiron/sqlx
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: false
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ Step-by-step instructions are available at [docs.stashapp.cc/installation](https
|
|||
> **macOS Users**
|
||||
>
|
||||
> As of version 0.29.0, Stash requires _macOS 11 Big Sur_ or later.
|
||||
> Stash can still be run through docker on older versions of macOS.
|
||||
> As of version 0.32.0, Stash requires _macOS 12 Monterey_ or later.
|
||||
> Stash can still be run through Docker on older versions of macOS.
|
||||
|
||||
<img src="docs/readme_assets/windows_logo.svg" width="100%" height="75"> Windows | <img src="docs/readme_assets/mac_logo.svg" width="100%" height="75"> macOS | <img src="docs/readme_assets/linux_logo.svg" width="100%" height="75"> Linux | <img src="docs/readme_assets/docker_logo.svg" width="100%" height="75"> Docker
|
||||
:---:|:---:|:---:|:---:
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ func recoverPanic() {
|
|||
exitCode = 1
|
||||
logger.Errorf("panic: %v\n%s", err, debug.Stack())
|
||||
if desktop.IsDesktop() {
|
||||
desktop.FatalError(fmt.Errorf("Panic: %v", err))
|
||||
desktop.FatalError(fmt.Errorf("panic: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ WORKDIR /tmp/osxcross
|
|||
ARG OSXCROSS_REVISION=5e1b71fcceb23952f3229995edca1b6231525b5b
|
||||
ADD --checksum=sha256:d3f771bbc20612fea577b18a71be3af2eb5ad2dd44624196cf55de866d008647 https://codeload.github.com/tpoechtrager/osxcross/tar.gz/${OSXCROSS_REVISION} /tmp/osxcross.tar.gz
|
||||
|
||||
ARG OSX_SDK_VERSION=11.3
|
||||
ARG OSX_SDK_VERSION=12.3
|
||||
ARG OSX_SDK_DOWNLOAD_FILE=MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
|
||||
ARG OSX_SDK_DOWNLOAD_URL=https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
|
||||
ADD --checksum=sha256:cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4 ${OSX_SDK_DOWNLOAD_URL} /tmp/osxcross/tarballs/${OSX_SDK_DOWNLOAD_FILE}
|
||||
ARG OSX_SDK_DOWNLOAD_URL=https://github.com/joseluisq/macosx-sdks/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
|
||||
ADD --checksum=sha256:3abd261ceb483c44295a6623fdffe5d44fc4ac2c872526576ec5ab5ad0f6e26c ${OSX_SDK_DOWNLOAD_URL} /tmp/osxcross/tarballs/${OSX_SDK_DOWNLOAD_FILE}
|
||||
|
||||
ENV UNATTENDED=yes \
|
||||
SDK_VERSION=${OSX_SDK_VERSION} \
|
||||
OSX_VERSION_MIN=10.10
|
||||
OSX_VERSION_MIN=12.0
|
||||
RUN apt update && \
|
||||
apt install -y --no-install-recommends \
|
||||
bash ca-certificates clang cmake git patch libssl-dev bzip2 cpio libbz2-dev libxml2-dev make python3 xz-utils zlib1g-dev
|
||||
|
|
@ -46,7 +46,7 @@ RUN cd /opt/cross-freebsd/usr/lib && \
|
|||
ln -s libc++.so libstdc++.so
|
||||
|
||||
### BUILDER
|
||||
FROM golang:1.24.3 AS builder
|
||||
FROM golang:1.25.9 AS builder
|
||||
ENV PATH=/opt/osx-ndk-x86/bin:$PATH
|
||||
|
||||
# copy in nodejs instead of using nodesource :thumbsup:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
host=ghcr.io
|
||||
user=stashapp
|
||||
repo=compiler
|
||||
version=13
|
||||
version=14
|
||||
|
||||
VERSION_IMAGE = ${host}/${user}/${repo}:${version}
|
||||
LATEST_IMAGE = ${host}/${user}/${repo}:latest
|
||||
|
|
|
|||
24
go.mod
24
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/stashapp/stash
|
||||
|
||||
go 1.24.3
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.73
|
||||
|
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||
github.com/doug-martin/goqu/v9 v9.18.0
|
||||
github.com/feederbox826/gosx-notifier v0.2.2
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/httplog v0.3.1
|
||||
|
|
@ -30,7 +31,6 @@ require (
|
|||
github.com/jinzhu/copier v0.4.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2
|
||||
github.com/kermieisinthehouse/systray v1.2.4
|
||||
github.com/knadh/koanf/parsers/yaml v1.1.0
|
||||
github.com/knadh/koanf/providers/env v1.1.0
|
||||
|
|
@ -56,12 +56,12 @@ require (
|
|||
github.com/vektra/mockery/v2 v2.10.0
|
||||
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
|
||||
github.com/zencoder/go-dash/v3 v3.0.2
|
||||
golang.org/x/crypto v0.45.0
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/sys v0.38.0
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/text v0.31.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/image v0.38.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/term v0.40.0
|
||||
golang.org/x/text v0.35.0
|
||||
golang.org/x/time v0.10.0
|
||||
gopkg.in/guregu/null.v4 v4.0.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
|
|
@ -70,7 +70,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/antchfx/xpath v1.3.5 // indirect
|
||||
github.com/antchfx/xpath v1.3.6 // indirect
|
||||
github.com/asticode/go-astikit v0.20.0 // indirect
|
||||
github.com/asticode/go-astits v1.8.0 // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
|
|
@ -121,9 +121,9 @@ require (
|
|||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.3 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
43
go.sum
43
go.sum
|
|
@ -87,8 +87,9 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
|
|||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
|
||||
github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
|
||||
github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=
|
||||
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antchfx/xpath v1.3.6 h1:s0y+ElRRtTQdfHP609qFu0+c6bglDv20pqOViQjjdPI=
|
||||
github.com/antchfx/xpath v1.3.6/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
|
|
@ -187,6 +188,8 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E
|
|||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/feederbox826/gosx-notifier v0.2.2 h1:26NkaJZ8Wzptx82R46c9pkVAcFwGSU7kxWrOKmRWlC0=
|
||||
github.com/feederbox826/gosx-notifier v0.2.2/go.mod h1:R6rqw7VuwuiCuvsr7EOONmWq++CRA5Ijmkmx75/C3Fs=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
|
|
@ -389,8 +392,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2 h1:KV0KBeKK2B24kIHY7iK0jgS64Q05f4oB+hUZmsPodxQ=
|
||||
github.com/kermieisinthehouse/gosx-notifier v0.1.2/go.mod h1:xyWT07azFtUOcHl96qMVvKhvKzsMcS7rKTHQyv8WTho=
|
||||
github.com/kermieisinthehouse/systray v1.2.4 h1:pdH5vnl+KKjRrVCRU4g/2W1/0HVzuuJ6WXHlPPHYY6s=
|
||||
github.com/kermieisinthehouse/systray v1.2.4/go.mod h1:axh6C/jNuSyC0QGtidZJURc9h+h41HNoMySoLVrhVR4=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
|
|
@ -667,8 +668,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
|
|||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -682,8 +683,8 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
|
|||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.38.0 h1:5l+q+Y9JDC7mBOMjo4/aPhMDcxEptsX+Tt3GgRQRPuE=
|
||||
golang.org/x/image v0.38.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
@ -714,8 +715,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
@ -770,8 +771,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
|
@ -806,8 +807,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
@ -894,8 +895,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
@ -905,8 +906,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
@ -923,8 +924,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
@ -992,8 +993,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
|||
|
|
@ -148,12 +148,12 @@ func makeGithubRequest(ctx context.Context, url string, output interface{}) erro
|
|||
response, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
//nolint:staticcheck // ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API request failed: %w", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
//nolint:staticcheck // ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API request failed: %s", response.Status)
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ func makeGithubRequest(ctx context.Context, url string, output interface{}) erro
|
|||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
//lint:ignore ST1005 Github is a proper capitalized noun
|
||||
//nolint:staticcheck // ST1005 Github is a proper capitalized noun
|
||||
return fmt.Errorf("Github API read response failed: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -295,10 +295,10 @@ func printLatestVersion(ctx context.Context) {
|
|||
logger.Errorf("Couldn't retrieve latest version: %v", err)
|
||||
} else {
|
||||
_, githash, _ := build.Version()
|
||||
switch {
|
||||
case githash == "":
|
||||
switch githash {
|
||||
case "":
|
||||
logger.Infof("Latest version: %s (%s)", latestRelease.Version, latestRelease.ShortHash)
|
||||
case githash == latestRelease.ShortHash:
|
||||
case latestRelease.ShortHash:
|
||||
logger.Infof("Version %s (%s) is already the latest released", latestRelease.Version, latestRelease.ShortHash)
|
||||
default:
|
||||
logger.Infof("New version available: %s (%s)", latestRelease.Version, latestRelease.ShortHash)
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePat
|
|||
objHash := obj.GetHash(config.GetVideoFileNamingAlgorithm())
|
||||
vttPath := builder.GetSpriteVTTURL(objHash)
|
||||
spritePath := builder.GetSpriteURL(objHash)
|
||||
funscriptPath := builder.GetFunscriptURL()
|
||||
funscriptPath := builder.GetFunscriptURL(config.GetAPIKey()).String()
|
||||
captionBasePath := builder.GetCaptionURL()
|
||||
interactiveHeatmap := builder.GetInteractiveHeatmapURL()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import (
|
|||
func refreshPackageType(typeArg PackageType) {
|
||||
mgr := manager.GetInstance()
|
||||
|
||||
if typeArg == PackageTypePlugin {
|
||||
switch typeArg {
|
||||
case PackageTypePlugin:
|
||||
mgr.RefreshPluginCache()
|
||||
} else if typeArg == PackageTypeScraper {
|
||||
case PackageTypeScraper:
|
||||
mgr.RefreshScraperCache()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -654,7 +654,7 @@ func (r *mutationResolver) PerformerMerge(ctx context.Context, input PerformerMe
|
|||
}
|
||||
legacyURLs := legacyPerformerURLsFromInput(*input.Values, translator)
|
||||
if legacyURLs.AnySet() {
|
||||
return nil, errors.New("Merging legacy performer URLs is not supported")
|
||||
return nil, errors.New("merging legacy performer URLs is not supported")
|
||||
}
|
||||
|
||||
if input.Values.Image != nil {
|
||||
|
|
|
|||
|
|
@ -57,8 +57,20 @@ func (b SceneURLBuilder) GetScreenshotURL() string {
|
|||
return b.BaseURL + "/scene/" + b.SceneID + "/screenshot?t=" + b.UpdatedAt
|
||||
}
|
||||
|
||||
func (b SceneURLBuilder) GetFunscriptURL() string {
|
||||
return b.BaseURL + "/scene/" + b.SceneID + "/funscript"
|
||||
func (b SceneURLBuilder) GetFunscriptURL(apiKey string) *url.URL {
|
||||
u, err := url.Parse(fmt.Sprintf("%s/scene/%s/funscript", b.BaseURL, b.SceneID))
|
||||
if err != nil {
|
||||
// shouldn't happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if apiKey != "" {
|
||||
v := u.Query()
|
||||
v.Set("apikey", apiKey)
|
||||
u.RawQuery = v.Encode()
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (b SceneURLBuilder) GetCaptionURL() string {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
|
||||
gosxnotifier "github.com/kermieisinthehouse/gosx-notifier"
|
||||
gosxnotifier "github.com/feederbox826/gosx-notifier"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ type MetadataOptions struct {
|
|||
SetCoverImage *bool `json:"setCoverImage"`
|
||||
SetOrganized *bool `json:"setOrganized"`
|
||||
// defaults to true if not provided
|
||||
|
||||
// Deprecated: use PerformerGenders instead
|
||||
IncludeMalePerformers *bool `json:"includeMalePerformers"`
|
||||
|
||||
// Filter to only include performers with these genders. If not provided, all genders are included.
|
||||
PerformerGenders []models.GenderEnum `json:"performerGenders"`
|
||||
// defaults to true if not provided
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ type SceneMissingHashCounter interface {
|
|||
// will ensure that all oshash values are set on all scenes.
|
||||
func ValidateVideoFileNamingAlgorithm(ctx context.Context, qb SceneMissingHashCounter, newValue models.HashAlgorithm) error {
|
||||
// if algorithm is being set to MD5, then all checksums must be present
|
||||
if newValue == models.HashAlgorithmMd5 {
|
||||
switch newValue {
|
||||
case models.HashAlgorithmMd5:
|
||||
missingMD5, err := qb.CountMissingChecksum(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -31,7 +32,7 @@ func ValidateVideoFileNamingAlgorithm(ctx context.Context, qb SceneMissingHashCo
|
|||
if missingMD5 > 0 {
|
||||
return errors.New("some checksums are missing on scenes. Run Scan with calculateMD5 set to true")
|
||||
}
|
||||
} else if newValue == models.HashAlgorithmOshash {
|
||||
case models.HashAlgorithmOshash:
|
||||
missingOSHash, err := qb.CountMissingOSHash(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ func ConvertFunscriptToCSV(funscriptPath string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// I don't know whether the csv format requires int or float, so for now we'll use int
|
||||
buffer.WriteString(fmt.Sprintf("%d,%d\r\n", int(math.Round(action.At)), pos))
|
||||
fmt.Fprintf(&buffer, "%d,%d\r\n", int(math.Round(action.At)), pos)
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,9 +76,10 @@ func performImport(ctx context.Context, i importer, duplicateBehaviour ImportDup
|
|||
var id int
|
||||
|
||||
if existing != nil {
|
||||
if duplicateBehaviour == ImportDuplicateEnumFail {
|
||||
switch duplicateBehaviour {
|
||||
case ImportDuplicateEnumFail:
|
||||
return fmt.Errorf("existing object with name '%s'", name)
|
||||
} else if duplicateBehaviour == ImportDuplicateEnumIgnore {
|
||||
case ImportDuplicateEnumIgnore:
|
||||
logger.Infof("Skipping existing object %q", name)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -313,9 +313,36 @@ func (j *CleanGeneratedJob) cleanBlobFiles(ctx context.Context, progress *job.Pr
|
|||
return err
|
||||
}
|
||||
|
||||
// remove empty hash prefix subdirectories
|
||||
j.removeEmptyDirs(j.Paths.Blobs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (j *CleanGeneratedJob) removeEmptyDirs(root string) {
|
||||
entries, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(root, entry.Name())
|
||||
subEntries, err := os.ReadDir(dirPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(subEntries) == 0 {
|
||||
j.logDelete("removing empty directory: %s", entry.Name())
|
||||
j.deleteDir(dirPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (j *CleanGeneratedJob) getScenesWithHash(ctx context.Context, hash string) ([]*models.Scene, error) {
|
||||
fp := models.Fingerprint{
|
||||
Fingerprint: hash,
|
||||
|
|
@ -637,6 +664,8 @@ func (j *CleanGeneratedJob) cleanMarkerFiles(ctx context.Context, progress *job.
|
|||
return err
|
||||
}
|
||||
|
||||
j.removeEmptyDirs(j.Paths.Generated.Markers)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -730,5 +759,7 @@ func (j *CleanGeneratedJob) cleanThumbnailFiles(ctx context.Context, progress *j
|
|||
return err
|
||||
}
|
||||
|
||||
j.removeEmptyDirs(j.Paths.Generated.Thumbnails)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -262,7 +262,9 @@ func (j *GenerateJob) Execute(ctx context.Context, progress *job.Progress) error
|
|||
|
||||
for f := range queue {
|
||||
if job.IsCancelled(ctx) {
|
||||
break
|
||||
// keep draining the queue so the producer goroutine can finish
|
||||
// and release its read transaction, otherwise the DB stays locked
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (j *OptimiseDatabaseJob) Execute(ctx context.Context, progress *job.Progres
|
|||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error analyzing database: %w", err)
|
||||
return fmt.Errorf("error analyzing database: %w", err)
|
||||
}
|
||||
|
||||
progress.ExecuteTask("Vacuuming database", func() {
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ func (s *Manager) RunPluginTask(
|
|||
pluginProgress := make(chan float64)
|
||||
task, err := s.PluginCache.CreateTask(ctx, pluginID, taskName, args, pluginProgress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error creating plugin task: %w", err)
|
||||
return fmt.Errorf("error creating plugin task: %w", err)
|
||||
}
|
||||
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error running plugin task: %w", err)
|
||||
return fmt.Errorf("error running plugin task: %w", err)
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@ func (f *FFMpeg) InitHWSupport(ctx context.Context) {
|
|||
|
||||
// log if the initialization takes too long
|
||||
const hwInitLogTimeoutSecondsDefault = 5
|
||||
hwInitLogTimeoutSeconds := hwInitLogTimeoutSecondsDefault * time.Second
|
||||
timer := time.NewTimer(hwInitLogTimeoutSeconds)
|
||||
hwInitLogTimeout := hwInitLogTimeoutSecondsDefault * time.Second
|
||||
timer := time.NewTimer(hwInitLogTimeout)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-timer.C:
|
||||
logger.Warnf("[InitHWSupport] Hardware codec initialization is taking longer than %s...", hwInitLogTimeoutSeconds)
|
||||
logger.Warnf("[InitHWSupport] Hardware codec initialization is taking longer than %s...", hwInitLogTimeout)
|
||||
logger.Info("[InitHWSupport] Hardware encoding will not be available until initialization is complete.")
|
||||
case <-done:
|
||||
if !timer.Stop() {
|
||||
|
|
@ -96,16 +96,16 @@ func (f *FFMpeg) initHWSupport(ctx context.Context) {
|
|||
|
||||
// #6064 - add timeout to context to prevent hangs
|
||||
const hwTestTimeoutSecondsDefault = 10
|
||||
hwTestTimeoutSeconds := hwTestTimeoutSecondsDefault * time.Second
|
||||
hwTestTimeout := hwTestTimeoutSecondsDefault * time.Second
|
||||
|
||||
// allow timeout to be overridden with environment variable
|
||||
if timeout := os.Getenv("STASH_HW_TEST_TIMEOUT"); timeout != "" {
|
||||
if seconds, err := strconv.Atoi(timeout); err == nil {
|
||||
hwTestTimeoutSeconds = time.Duration(seconds) * time.Second
|
||||
hwTestTimeout = time.Duration(seconds) * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
testCtx, cancel := context.WithTimeout(ctx, hwTestTimeoutSeconds)
|
||||
testCtx, cancel := context.WithTimeout(ctx, hwTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := f.Command(testCtx, args)
|
||||
|
|
@ -117,7 +117,7 @@ func (f *FFMpeg) initHWSupport(ctx context.Context) {
|
|||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if testCtx.Err() != nil {
|
||||
logger.Debugf("[InitHWSupport] Codec %s test timed out after %s", codec, hwTestTimeoutSeconds)
|
||||
logger.Debugf("[InitHWSupport] Codec %s test timed out after %s", codec, hwTestTimeout)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ func (f *StashIgnoreFilter) collectIgnoreEntries(dir string, libraryRoot string)
|
|||
current := dir
|
||||
for {
|
||||
// Check if we're still within the library root.
|
||||
// nolint:staticcheck // QF1006 - we could make this the for condition
|
||||
// but I don't think it improves readability
|
||||
if !isPathInOrEqual(libraryRoot, current) {
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,19 +69,19 @@ type ScanHandler struct {
|
|||
|
||||
func (h *ScanHandler) validate() error {
|
||||
if h.CreatorUpdater == nil {
|
||||
return errors.New("CreatorUpdater is required")
|
||||
return errors.New("internal error: CreatorUpdater is required")
|
||||
}
|
||||
if h.ScanGenerator == nil {
|
||||
return errors.New("ScanGenerator is required")
|
||||
return errors.New("internal error: ScanGenerator is required")
|
||||
}
|
||||
if h.GalleryFinder == nil {
|
||||
return errors.New("GalleryFinder is required")
|
||||
return errors.New("internal error: GalleryFinder is required")
|
||||
}
|
||||
if h.ScanConfig == nil {
|
||||
return errors.New("ScanConfig is required")
|
||||
return errors.New("internal error: ScanConfig is required")
|
||||
}
|
||||
if h.Paths == nil {
|
||||
return errors.New("Paths is required")
|
||||
return errors.New("internal error: Paths is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -375,13 +375,13 @@ func (h *ScanHandler) getOrCreateGallery(ctx context.Context, f models.File) (*m
|
|||
if _, err := os.Stat(filepath.Join(folderPath, ".forcegallery")); err == nil {
|
||||
forceGallery = true
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("Could not test Path %s: %w", folderPath, err)
|
||||
return nil, fmt.Errorf("could not test Path %s: %w", folderPath, err)
|
||||
}
|
||||
exemptGallery := false
|
||||
if _, err := os.Stat(filepath.Join(folderPath, ".nogallery")); err == nil {
|
||||
exemptGallery = true
|
||||
} else if !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("Could not test Path %s: %w", folderPath, err)
|
||||
return nil, fmt.Errorf("could not test Path %s: %w", folderPath, err)
|
||||
}
|
||||
|
||||
if forceGallery || (h.ScanConfig.GetCreateGalleriesFromFolders() && !exemptGallery) {
|
||||
|
|
|
|||
|
|
@ -97,9 +97,10 @@ func (j *Job) TimeElapsed() time.Duration {
|
|||
}
|
||||
|
||||
func (j *Job) cancel() {
|
||||
if j.Status == StatusReady {
|
||||
switch j.Status {
|
||||
case StatusReady:
|
||||
j.Status = StatusCancelled
|
||||
} else if j.Status == StatusRunning {
|
||||
case StatusRunning:
|
||||
j.Status = StatusStopping
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ func (g Generator) generateConcatFile(chunkFiles []string) (fn string, err error
|
|||
for _, f := range chunkFiles {
|
||||
// files in concat file should be relative to concat
|
||||
relFile := filepath.Base(f)
|
||||
if _, err := w.WriteString(fmt.Sprintf("file '%s'\n", relFile)); err != nil {
|
||||
if _, err := fmt.Fprintf(w, "file '%s'\n", relFile); err != nil {
|
||||
return concatFile.Name(), fmt.Errorf("writing concat file: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,19 +57,19 @@ type ScanHandler struct {
|
|||
|
||||
func (h *ScanHandler) validate() error {
|
||||
if h.CreatorUpdater == nil {
|
||||
return errors.New("CreatorUpdater is required")
|
||||
return errors.New("internal error: CreatorUpdater is required")
|
||||
}
|
||||
if h.ScanGenerator == nil {
|
||||
return errors.New("ScanGenerator is required")
|
||||
return errors.New("internal error: ScanGenerator is required")
|
||||
}
|
||||
if h.CaptionUpdater == nil {
|
||||
return errors.New("CaptionUpdater is required")
|
||||
return errors.New("internal error: CaptionUpdater is required")
|
||||
}
|
||||
if !h.FileNamingAlgorithm.IsValid() {
|
||||
return errors.New("FileNamingAlgorithm is required")
|
||||
return errors.New("internal error: FileNamingAlgorithm is required")
|
||||
}
|
||||
if h.Paths == nil {
|
||||
return errors.New("Paths is required")
|
||||
return errors.New("internal error: Paths is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -70,11 +70,52 @@ func stringCriterionHandler(c *models.StringCriterionInput, column string) crite
|
|||
}
|
||||
}
|
||||
|
||||
func joinedStringCriterionHandler(c *models.StringCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func stringNoTrimCriterionHandler(c *models.StringCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if modifier := c.Modifier; c.Modifier.IsValid() {
|
||||
switch modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
f.whereClauses = append(f.whereClauses, getStringSearchClause([]string{column}, c.Value, false))
|
||||
case models.CriterionModifierExcludes:
|
||||
f.whereClauses = append(f.whereClauses, getStringSearchClause([]string{column}, c.Value, true))
|
||||
case models.CriterionModifierEquals:
|
||||
f.addWhere(column+" LIKE ?", c.Value)
|
||||
case models.CriterionModifierNotEquals:
|
||||
f.addWhere(column+" NOT LIKE ?", c.Value)
|
||||
case models.CriterionModifierMatchesRegex:
|
||||
if _, err := regexp.Compile(c.Value); err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
f.addWhere(fmt.Sprintf("(%s IS NOT NULL AND %[1]s regexp ?)", column), c.Value)
|
||||
case models.CriterionModifierNotMatchesRegex:
|
||||
if _, err := regexp.Compile(c.Value); err != nil {
|
||||
f.setError(err)
|
||||
return
|
||||
}
|
||||
f.addWhere(fmt.Sprintf("(%s IS NULL OR %[1]s NOT regexp ?)", column), c.Value)
|
||||
case models.CriterionModifierIsNull:
|
||||
f.addWhere("(" + column + " IS NULL)")
|
||||
case models.CriterionModifierNotNull:
|
||||
f.addWhere("(" + column + " IS NOT NULL)")
|
||||
default:
|
||||
panic("unsupported string filter modifier")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func joinedStringCriterionHandler(c *models.StringCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull || c.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
stringCriterionHandler(c, column)(ctx, f)
|
||||
}
|
||||
|
|
@ -104,16 +145,20 @@ func enumCriterionHandler(modifier models.CriterionModifier, values []string, co
|
|||
}
|
||||
}
|
||||
|
||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func pathCriterionHandler(c *models.StringCriterionInput, pathColumn string, basenameColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
}
|
||||
addWildcards := true
|
||||
not := false
|
||||
|
||||
if modifier := c.Modifier; c.Modifier.IsValid() {
|
||||
if addJoinFn != nil {
|
||||
joinType := joinTypeInner
|
||||
if modifier == models.CriterionModifierIsNull || modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
addWildcards := true
|
||||
not := false
|
||||
|
||||
switch modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
f.whereClauses = append(f.whereClauses, getPathSearchClauseMany(pathColumn, basenameColumn, c.Value, addWildcards, not))
|
||||
|
|
@ -194,11 +239,15 @@ func getPathSearchClauseMany(pathColumn, basenameColumn, p string, addWildcards,
|
|||
return getPathSearchClause(pathColumn, basenameColumn, trimmedQuery, addWildcards, not)
|
||||
}
|
||||
|
||||
func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getIntCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -206,11 +255,15 @@ func intCriterionHandler(c *models.IntCriterionInput, column string, addJoinFn f
|
|||
}
|
||||
}
|
||||
|
||||
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getFloatCriterionWhereClause(column, *c)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -218,11 +271,15 @@ func floatCriterionHandler(c *models.FloatCriterionInput, column string, addJoin
|
|||
}
|
||||
}
|
||||
|
||||
func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if durationFilter != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if durationFilter.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
clause, args := getIntCriterionWhereClause("cast("+column+" as int)", *durationFilter)
|
||||
f.addWhere(clause, args...)
|
||||
|
|
@ -230,11 +287,11 @@ func floatIntCriterionHandler(durationFilter *models.IntCriterionInput, column s
|
|||
}
|
||||
}
|
||||
|
||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
var v string
|
||||
if *c {
|
||||
|
|
@ -289,11 +346,11 @@ func yearFilterCriterionHandler(year *models.IntCriterionInput, col string) crit
|
|||
}
|
||||
}
|
||||
|
||||
func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if resolution != nil && resolution.Value.IsValid() {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
|
||||
mn := resolution.Value.GetMinResolution()
|
||||
|
|
@ -315,11 +372,11 @@ func resolutionCriterionHandler(resolution *models.ResolutionCriterionInput, hei
|
|||
}
|
||||
}
|
||||
|
||||
func orientationCriterionHandler(orientation *models.OrientationCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func orientationCriterionHandler(orientation *models.OrientationCriterionInput, heightColumn string, widthColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if orientation != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
addJoinFn(f, joinTypeInner)
|
||||
}
|
||||
|
||||
var clauses []sqlClause
|
||||
|
|
@ -362,7 +419,7 @@ type joinedMultiCriterionHandlerBuilder struct {
|
|||
// foreign key of the foreign object on the join table
|
||||
foreignFK string
|
||||
|
||||
addJoinTable func(f *filterBuilder)
|
||||
addJoinTable func(f *filterBuilder, joinType joinType)
|
||||
}
|
||||
|
||||
func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -378,11 +435,13 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotNull {
|
||||
var notClause string
|
||||
joinType := joinTypeLeft
|
||||
if criterion.Modifier == models.CriterionModifierNotNull {
|
||||
notClause = "NOT"
|
||||
joinType = joinTypeInner
|
||||
}
|
||||
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinType)
|
||||
|
||||
f.addWhere(utils.StrFormat("{table}.{column} IS {not} NULL", utils.StrFormatMap{
|
||||
"table": joinAlias,
|
||||
|
|
@ -415,11 +474,11 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
switch criterion.Modifier {
|
||||
case models.CriterionModifierIncludes:
|
||||
// includes any of the provided ids
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
whereClause = fmt.Sprintf("%s.%s IN %s", joinAlias, m.foreignFK, getInBinding(len(criterion.Value)))
|
||||
case models.CriterionModifierEquals:
|
||||
// includes only the provided ids
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
whereClause = utils.StrFormat("{joinAlias}.{foreignFK} IN {inBinding} AND (SELECT COUNT(*) FROM {joinTable} s WHERE s.{primaryFK} = {primaryTable}.id) = ?", utils.StrFormatMap{
|
||||
"joinAlias": joinAlias,
|
||||
"foreignFK": m.foreignFK,
|
||||
|
|
@ -434,7 +493,7 @@ func (m *joinedMultiCriterionHandlerBuilder) handler(c *models.MultiCriterionInp
|
|||
f.setError(fmt.Errorf("not equals modifier is not supported for multi criterion input"))
|
||||
case models.CriterionModifierIncludesAll:
|
||||
// includes all of the provided ids
|
||||
m.addJoinTable(f)
|
||||
m.addJoinTable(f, joinTypeInner)
|
||||
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))
|
||||
}
|
||||
|
|
@ -468,7 +527,7 @@ type multiCriterionHandlerBuilder struct {
|
|||
foreignFK string
|
||||
|
||||
// function that will be called to perform any necessary joins
|
||||
addJoinsFunc func(f *filterBuilder)
|
||||
addJoinsFunc func(f *filterBuilder, joinType joinType)
|
||||
}
|
||||
|
||||
func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -500,7 +559,7 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
|
|||
}
|
||||
|
||||
if m.addJoinsFunc != nil {
|
||||
m.addJoinsFunc(f)
|
||||
m.addJoinsFunc(f, joinTypeInner)
|
||||
}
|
||||
|
||||
whereClause, havingClause := getMultiCriterionClause(m.primaryTable, m.foreignTable, m.joinTable, m.primaryFK, m.foreignFK, criterion)
|
||||
|
|
@ -536,7 +595,7 @@ type stringListCriterionHandlerBuilder struct {
|
|||
// string field on the join table
|
||||
stringColumn string
|
||||
|
||||
addJoinTable func(f *filterBuilder)
|
||||
addJoinTable func(f *filterBuilder, joinType joinType)
|
||||
excludeHandler func(f *filterBuilder, criterion *models.StringCriterionInput)
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +629,11 @@ func (m *stringListCriterionHandlerBuilder) handler(criterion *models.StringCrit
|
|||
// Modifier: models.CriterionModifierNotNull,
|
||||
// }, m.joinTable+"."+m.stringColumn)(ctx, f)
|
||||
} else {
|
||||
m.addJoinTable(f)
|
||||
joinType := joinTypeInner
|
||||
if criterion.Modifier == models.CriterionModifierIsNull || criterion.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
m.addJoinTable(f, joinType)
|
||||
stringCriterionHandler(criterion, m.joinTable+"."+m.stringColumn)(ctx, f)
|
||||
}
|
||||
}
|
||||
|
|
@ -1028,14 +1091,18 @@ func (h *stashIDCriterionHandler) handle(ctx context.Context, f *filterBuilder)
|
|||
joinClause += fmt.Sprintf(" AND %s.endpoint = '%s'", t, *h.c.Endpoint)
|
||||
}
|
||||
|
||||
f.addLeftJoin(stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
joinType := joinTypeInner
|
||||
if h.c.Modifier == models.CriterionModifierIsNull || h.c.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
f.addJoin(joinType, stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
|
||||
v := ""
|
||||
if h.c.StashID != nil {
|
||||
v = *h.c.StashID
|
||||
}
|
||||
|
||||
stringCriterionHandler(&models.StringCriterionInput{
|
||||
stringNoTrimCriterionHandler(&models.StringCriterionInput{
|
||||
Value: v,
|
||||
Modifier: h.c.Modifier,
|
||||
}, t+".stash_id")(ctx, f)
|
||||
|
|
@ -1064,7 +1131,12 @@ func (h *stashIDsCriterionHandler) handle(ctx context.Context, f *filterBuilder)
|
|||
joinClause += fmt.Sprintf(" AND %s.endpoint = '%s'", t, *h.c.Endpoint)
|
||||
}
|
||||
|
||||
f.addLeftJoin(stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
joinType := joinTypeInner
|
||||
if h.c.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
|
||||
f.addJoin(joinType, stashIDRepo.tableName, h.stashIDTableAs, joinClause)
|
||||
|
||||
switch h.c.Modifier {
|
||||
case models.CriterionModifierIsNull:
|
||||
|
|
|
|||
|
|
@ -300,15 +300,19 @@ func (qb *videoFileFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *videoFileFilterHandler) addVideoFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(videoFileTable, "", "video_files.file_id = files.id")
|
||||
func (qb *videoFileFilterHandler) addVideoFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, videoFileTable, "", "video_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (qb *videoFileFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func (qb *videoFileFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if codec != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if codec.Modifier == models.CriterionModifierIsNull || codec.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
|
||||
stringCriterionHandler(codec, codecColumn)(ctx, f)
|
||||
|
|
@ -322,8 +326,8 @@ func (qb *videoFileFilterHandler) captionCriterionHandler(captions *models.Strin
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: videoCaptionsTable,
|
||||
stringColumn: captionCodeColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
f.addLeftJoin(videoCaptionsTable, "", "video_captions.file_id = files.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, videoCaptionsTable, "", "video_captions.file_id = files.id")
|
||||
},
|
||||
excludeHandler: func(f *filterBuilder, criterion *models.StringCriterionInput) {
|
||||
excludeClause := `files.id NOT IN (
|
||||
|
|
@ -361,6 +365,6 @@ func (qb *imageFileFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *imageFileFilterHandler) addImageFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(imageFileTable, "", "image_files.file_id = files.id")
|
||||
func (qb *imageFileFilterHandler) addImageFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, imageFileTable, "", "image_files.file_id = files.id")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,11 +90,18 @@ func andClauses(clauses ...sqlClause) sqlClause {
|
|||
return joinClauses("AND", clauses...)
|
||||
}
|
||||
|
||||
type joinType string
|
||||
|
||||
const (
|
||||
joinTypeLeft joinType = "LEFT"
|
||||
joinTypeInner joinType = "INNER"
|
||||
)
|
||||
|
||||
type join struct {
|
||||
table string
|
||||
as string
|
||||
onClause string
|
||||
joinType string
|
||||
joinType joinType
|
||||
args []interface{}
|
||||
|
||||
// if true, indicates this is required for sorting only
|
||||
|
|
@ -115,15 +122,19 @@ func (j join) alias() string {
|
|||
return j.as
|
||||
}
|
||||
|
||||
func (j join) getJoinType() joinType {
|
||||
if j.joinType == "" {
|
||||
return joinTypeLeft
|
||||
}
|
||||
return j.joinType
|
||||
}
|
||||
|
||||
func (j join) toSQL() string {
|
||||
asStr := ""
|
||||
joinStr := j.joinType
|
||||
joinStr := j.getJoinType()
|
||||
if j.as != "" && j.as != j.table {
|
||||
asStr = " AS " + j.as
|
||||
}
|
||||
if j.joinType == "" {
|
||||
joinStr = "LEFT"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s JOIN %s%s ON %s", joinStr, j.table, asStr, j.onClause)
|
||||
}
|
||||
|
|
@ -141,6 +152,12 @@ func (j *joins) addUnique(newJoin join) bool {
|
|||
if !newJoin.sort && jj.sort {
|
||||
(*j)[i].sort = false
|
||||
}
|
||||
|
||||
// if the new join is inner, override existing left join
|
||||
if newJoin.getJoinType() == joinTypeInner && jj.getJoinType() == joinTypeLeft {
|
||||
(*j)[i].joinType = joinTypeInner
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -243,6 +260,23 @@ func (f *filterBuilder) not(n *filterBuilder) {
|
|||
f.subFilterOp = notOp
|
||||
}
|
||||
|
||||
// addJoin adds a join to the filter. The join is expressed in SQL as:
|
||||
// <joinType> JOIN <table> [AS <as>] ON <onClause>
|
||||
// The AS is omitted if as is empty.
|
||||
// This method does not add a join if it its alias/table name is already
|
||||
// present in another existing join.
|
||||
func (f *filterBuilder) addJoin(joinType joinType, table, as, onClause string, args ...interface{}) {
|
||||
newJoin := join{
|
||||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: joinType,
|
||||
args: args,
|
||||
}
|
||||
|
||||
f.joins.add(newJoin)
|
||||
}
|
||||
|
||||
// addLeftJoin adds a left join to the filter. The join is expressed in SQL as:
|
||||
// LEFT JOIN <table> [AS <as>] ON <onClause>
|
||||
// The AS is omitted if as is empty.
|
||||
|
|
@ -253,7 +287,7 @@ func (f *filterBuilder) addLeftJoin(table, as, onClause string, args ...interfac
|
|||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: "LEFT",
|
||||
joinType: joinTypeLeft,
|
||||
args: args,
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +304,7 @@ func (f *filterBuilder) addInnerJoin(table, as, onClause string, args ...interfa
|
|||
table: table,
|
||||
as: as,
|
||||
onClause: onClause,
|
||||
joinType: "INNER",
|
||||
joinType: joinTypeInner,
|
||||
args: args,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -193,15 +193,15 @@ func (qb *galleryFilterHandler) urlsCriterionHandler(url *models.StringCriterion
|
|||
primaryFK: galleryIDColumn,
|
||||
joinTable: galleriesURLsTable,
|
||||
stringColumn: galleriesURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
galleriesURLsTableMgr.join(f, "", "galleries.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
galleriesURLsTableMgr.join(f, joinType, "", "galleries.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *galleryFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *galleryFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: galleryTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -353,7 +353,7 @@ func (qb *galleryFilterHandler) missingCriterionHandler(isMissing *string) crite
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
galleriesURLsTableMgr.join(f, "", "galleries.id")
|
||||
galleriesURLsTableMgr.leftJoin(f, "", "galleries.id")
|
||||
f.addWhere("gallery_urls.url IS NULL")
|
||||
case "scenes":
|
||||
f.addLeftJoin("scenes_galleries", "scenes_join", "scenes_join.gallery_id = galleries.id")
|
||||
|
|
@ -361,12 +361,12 @@ func (qb *galleryFilterHandler) missingCriterionHandler(isMissing *string) crite
|
|||
case "studio":
|
||||
f.addWhere("galleries.studio_id IS NULL")
|
||||
case "performers":
|
||||
galleryRepository.performers.join(f, "performers_join", "galleries.id")
|
||||
galleryRepository.performers.leftJoin(f, "performers_join", "galleries.id")
|
||||
f.addWhere("performers_join.gallery_id IS NULL")
|
||||
case "date":
|
||||
f.addWhere("galleries.date IS NULL OR galleries.date IS \"\"")
|
||||
case "tags":
|
||||
galleryRepository.tags.join(f, "tags_join", "galleries.id")
|
||||
galleryRepository.tags.leftJoin(f, "tags_join", "galleries.id")
|
||||
f.addWhere("tags_join.gallery_id IS NULL")
|
||||
case "cover":
|
||||
f.addLeftJoin("galleries_images", "cover_join", "cover_join.gallery_id = galleries.id AND cover_join.cover = 1")
|
||||
|
|
@ -410,9 +410,9 @@ func (qb *galleryFilterHandler) tagCountCriterionHandler(tagCount *models.IntCri
|
|||
}
|
||||
|
||||
func (qb *galleryFilterHandler) scenesCriterionHandler(scenes *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
galleryRepository.scenes.join(f, "", "galleries.id")
|
||||
f.addLeftJoin("scenes", "", "scenes_galleries.scene_id = scenes.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
galleryRepository.scenes.join(f, joinType, "", "galleries.id")
|
||||
f.addJoin(joinType, "scenes", "", "scenes_galleries.scene_id = scenes.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(sceneTable, galleriesScenesTable, "scene_id", addJoinsFunc)
|
||||
return h.handler(scenes)
|
||||
|
|
@ -426,8 +426,8 @@ func (qb *galleryFilterHandler) performersCriterionHandler(performers *models.Mu
|
|||
primaryFK: galleryIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
galleryRepository.performers.join(f, "performers_join", "galleries.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
galleryRepository.performers.join(f, joinType, "performers_join", "galleries.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -515,7 +515,7 @@ func (qb *galleryFilterHandler) performerAgeCriterionHandler(performerAge *model
|
|||
func (qb *galleryFilterHandler) averageResolutionCriterionHandler(resolution *models.ResolutionCriterionInput) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if resolution != nil && resolution.Value.IsValid() {
|
||||
galleryRepository.images.join(f, "images_join", "galleries.id")
|
||||
galleryRepository.images.leftJoin(f, "images_join", "galleries.id")
|
||||
f.addLeftJoin("images", "", "images_join.image_id = images.id")
|
||||
f.addLeftJoin("images_files", "", "images.id = images_files.image_id")
|
||||
f.addLeftJoin("image_files", "", "images_files.file_id = image_files.file_id")
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ func (qb *groupFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
f.addLeftJoin("groups_scenes", "", "groups_scenes.group_id = groups.id")
|
||||
f.addWhere("groups_scenes.scene_id IS NULL")
|
||||
case "url":
|
||||
groupsURLsTableMgr.join(f, "", "groups.id")
|
||||
groupsURLsTableMgr.leftJoin(f, "", "groups.id")
|
||||
f.addWhere("group_urls.url IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("groups.studio_id IS NULL")
|
||||
|
|
@ -129,7 +129,7 @@ func (qb *groupFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
f.addLeftJoin("performers_scenes", "ps_perf", "gs_perf.scene_id = ps_perf.scene_id")
|
||||
f.addWhere("ps_perf.performer_id IS NULL")
|
||||
case "tags":
|
||||
groupRepository.tags.join(f, "tags_join", "groups.id")
|
||||
groupRepository.tags.leftJoin(f, "tags_join", "groups.id")
|
||||
f.addWhere("tags_join.group_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
@ -150,8 +150,8 @@ func (qb *groupFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: groupIDColumn,
|
||||
joinTable: groupURLsTable,
|
||||
stringColumn: groupURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
groupsURLsTableMgr.join(f, "", "groups.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
groupsURLsTableMgr.join(f, joinType, "", "groups.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,23 +123,23 @@ type imageRepositoryType struct {
|
|||
files filesRepository
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addImagesFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(imagesFilesTable, "", "images_files.image_id = images.id")
|
||||
func (r *imageRepositoryType) addImagesFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, imagesFilesTable, "", "images_files.image_id = images.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addFilesTable(f *filterBuilder) {
|
||||
r.addImagesFilesTable(f)
|
||||
f.addLeftJoin(fileTable, "", "images_files.file_id = files.id")
|
||||
func (r *imageRepositoryType) addFilesTable(f *filterBuilder, joinType joinType) {
|
||||
r.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fileTable, "", "images_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addFoldersTable(f *filterBuilder) {
|
||||
r.addFilesTable(f)
|
||||
f.addLeftJoin(folderTable, "", "files.parent_folder_id = folders.id")
|
||||
func (r *imageRepositoryType) addFoldersTable(f *filterBuilder, joinType joinType) {
|
||||
r.addFilesTable(f, joinType)
|
||||
f.addJoin(joinType, folderTable, "", "files.parent_folder_id = folders.id")
|
||||
}
|
||||
|
||||
func (r *imageRepositoryType) addImageFilesTable(f *filterBuilder) {
|
||||
r.addImagesFilesTable(f)
|
||||
f.addLeftJoin(imageFileTable, "", "image_files.file_id = images_files.file_id")
|
||||
func (r *imageRepositoryType) addImageFilesTable(f *filterBuilder, joinType joinType) {
|
||||
r.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, imageFileTable, "", "image_files.file_id = images_files.file_id")
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -56,8 +56,12 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
intCriterionHandler(imageFilter.ID, "images.id", nil),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if imageFilter.Checksum != nil {
|
||||
imageRepository.addImagesFilesTable(f)
|
||||
f.addInnerJoin(fingerprintTable, "fingerprints_md5", "images_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
joinType := joinTypeInner
|
||||
if imageFilter.Checksum.Modifier == models.CriterionModifierIsNull || imageFilter.Checksum.Modifier == models.CriterionModifierNotMatchesRegex {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
imageRepository.addImagesFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_md5", "images_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(imageFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f)
|
||||
|
|
@ -65,8 +69,8 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
&phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
imageRepository.addImagesFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "images_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
imageRepository.addImagesFilesTable(f, joinTypeInner)
|
||||
f.addInnerJoin(fingerprintTable, "fingerprints_phash", "images_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: imageFilter.PhashDistance,
|
||||
},
|
||||
|
|
@ -148,8 +152,8 @@ func (qb *imageFilterHandler) criterionHandler() criterionHandler {
|
|||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
imageRepository.addFilesTable(f)
|
||||
imageRepository.addFoldersTable(f)
|
||||
imageRepository.addFilesTable(f, joinTypeInner)
|
||||
imageRepository.addFoldersTable(f, joinTypeInner)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
|
|
@ -172,18 +176,18 @@ func (qb *imageFilterHandler) missingCriterionHandler(isMissing *string) criteri
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
imagesURLsTableMgr.join(f, "", "images.id")
|
||||
imagesURLsTableMgr.leftJoin(f, "", "images.id")
|
||||
f.addWhere("image_urls.url IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("images.studio_id IS NULL")
|
||||
case "performers":
|
||||
imageRepository.performers.join(f, "performers_join", "images.id")
|
||||
imageRepository.performers.leftJoin(f, "performers_join", "images.id")
|
||||
f.addWhere("performers_join.image_id IS NULL")
|
||||
case "galleries":
|
||||
imageRepository.galleries.join(f, "galleries_join", "images.id")
|
||||
imageRepository.galleries.leftJoin(f, "galleries_join", "images.id")
|
||||
f.addWhere("galleries_join.image_id IS NULL")
|
||||
case "tags":
|
||||
imageRepository.tags.join(f, "tags_join", "images.id")
|
||||
imageRepository.tags.leftJoin(f, "tags_join", "images.id")
|
||||
f.addWhere("tags_join.image_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
@ -204,15 +208,15 @@ func (qb *imageFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: imageIDColumn,
|
||||
joinTable: imagesURLsTable,
|
||||
stringColumn: imageURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
imagesURLsTableMgr.join(f, "", "images.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
imagesURLsTableMgr.join(f, joinType, "", "images.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *imageFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *imageFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: imageTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -249,7 +253,7 @@ func (qb *imageFilterHandler) tagCountCriterionHandler(tagCount *models.IntCrite
|
|||
}
|
||||
|
||||
func (qb *imageFilterHandler) galleriesCriterionHandler(galleries *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
if galleries.Modifier == models.CriterionModifierIncludes || galleries.Modifier == models.CriterionModifierIncludesAll {
|
||||
f.addInnerJoin(galleriesImagesTable, "", "galleries_images.image_id = images.id")
|
||||
f.addInnerJoin(galleryTable, "", "galleries_images.gallery_id = galleries.id")
|
||||
|
|
@ -268,8 +272,8 @@ func (qb *imageFilterHandler) performersCriterionHandler(performers *models.Mult
|
|||
primaryFK: imageIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
imageRepository.performers.join(f, "performers_join", "images.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
imageRepository.performers.join(f, joinType, "performers_join", "images.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ func (qb *performerFilterHandler) criterionHandler() criterionHandler {
|
|||
intCriterionHandler(filter.Weight, tableName+".weight", nil),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if filter.StashID != nil {
|
||||
performerRepository.stashIDs.join(f, "performer_stash_ids", "performers.id")
|
||||
performerRepository.stashIDs.leftJoin(f, "performer_stash_ids", "performers.id")
|
||||
stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -333,7 +333,7 @@ func (qb *performerFilterHandler) performerIsMissingCriterionHandler(isMissing *
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
performersURLsTableMgr.join(f, "", "performers.id")
|
||||
performersURLsTableMgr.leftJoin(f, "", "performers.id")
|
||||
f.addWhere("performer_urls.url IS NULL")
|
||||
case "scenes": // Deprecated: use `scene_count == 0` filter instead
|
||||
f.addLeftJoin(performersScenesTable, "scenes_join", "scenes_join.performer_id = performers.id")
|
||||
|
|
@ -341,10 +341,10 @@ func (qb *performerFilterHandler) performerIsMissingCriterionHandler(isMissing *
|
|||
case "image":
|
||||
f.addWhere("performers.image_blob IS NULL")
|
||||
case "stash_id":
|
||||
performersStashIDsTableMgr.join(f, "performer_stash_ids", "performers.id")
|
||||
performersStashIDsTableMgr.leftJoin(f, "performer_stash_ids", "performers.id")
|
||||
f.addWhere("performer_stash_ids.performer_id IS NULL")
|
||||
case "aliases":
|
||||
performersAliasesTableMgr.join(f, "", "performers.id")
|
||||
performersAliasesTableMgr.leftJoin(f, "", "performers.id")
|
||||
f.addWhere("performer_aliases.alias IS NULL")
|
||||
case "tags":
|
||||
f.addLeftJoin(performersTagsTable, "tags_join", "tags_join.performer_id = performers.id")
|
||||
|
|
@ -383,8 +383,8 @@ func (qb *performerFilterHandler) urlsCriterionHandler(url *models.StringCriteri
|
|||
primaryFK: performerIDColumn,
|
||||
joinTable: performerURLsTable,
|
||||
stringColumn: performerURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
performersURLsTableMgr.join(f, "", "performers.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
performersURLsTableMgr.join(f, joinType, "", "performers.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -397,8 +397,8 @@ func (qb *performerFilterHandler) aliasCriterionHandler(alias *models.StringCrit
|
|||
primaryFK: performerIDColumn,
|
||||
joinTable: performersAliasesTable,
|
||||
stringColumn: performerAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
performersAliasesTableMgr.join(f, "", "performers.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
performersAliasesTableMgr.join(f, joinType, "", "performers.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ func (r *updateRecord) setTimestamp(destField string, v models.OptionalTime) {
|
|||
}
|
||||
}
|
||||
|
||||
//nolint:golint,unused
|
||||
//nolint:unused
|
||||
func (r *updateRecord) setNullTimestamp(destField string, v models.OptionalTime) {
|
||||
if v.Set {
|
||||
r.set(destField, NullTimestampFromTimePtr(v.Ptr()))
|
||||
|
|
|
|||
|
|
@ -204,7 +204,15 @@ func (r *repository) newQuery() queryBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *repository) join(j joiner, as string, parentIDCol string) {
|
||||
func (r *repository) join(j joiner, t joinType, as string, parentIDCol string) {
|
||||
fn := r.innerJoin
|
||||
if t == joinTypeLeft {
|
||||
fn = r.leftJoin
|
||||
}
|
||||
fn(j, as, parentIDCol)
|
||||
}
|
||||
|
||||
func (r *repository) leftJoin(j joiner, as string, parentIDCol string) {
|
||||
t := r.tableName
|
||||
if as != "" {
|
||||
t = as
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
stringCriterionHandler(sceneFilter.Director, "scenes.director"),
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.Oshash != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_oshash", "scenes_files.file_id = fingerprints_oshash.file_id AND fingerprints_oshash.type = 'oshash'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Oshash.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_oshash", "scenes_files.file_id = fingerprints_oshash.file_id AND fingerprints_oshash.type = 'oshash'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(sceneFilter.Oshash, "fingerprints_oshash.fingerprint")(ctx, f)
|
||||
|
|
@ -72,8 +76,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.Checksum != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_md5", "scenes_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Checksum.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_md5", "scenes_files.file_id = fingerprints_md5.file_id AND fingerprints_md5.type = 'md5'")
|
||||
}
|
||||
|
||||
stringCriterionHandler(sceneFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f)
|
||||
|
|
@ -84,8 +92,12 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
// backwards compatibility
|
||||
h := phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
joinType := joinTypeInner
|
||||
if sceneFilter.Phash.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: &models.PhashDistanceCriterionInput{
|
||||
Value: sceneFilter.Phash.Value,
|
||||
|
|
@ -98,8 +110,9 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
&phashDistanceCriterionHandler{
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
const joinType = joinTypeInner
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
},
|
||||
criterion: sceneFilter.PhashDistance,
|
||||
},
|
||||
|
|
@ -122,7 +135,7 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if sceneFilter.StashID != nil {
|
||||
sceneRepository.stashIDs.join(f, "scene_stash_ids", "scenes.id")
|
||||
sceneRepository.stashIDs.leftJoin(f, "scene_stash_ids", "scenes.id")
|
||||
stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -236,8 +249,8 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
isRelated: true,
|
||||
},
|
||||
joinFn: func(f *filterBuilder) {
|
||||
qb.addFilesTable(f)
|
||||
qb.addFoldersTable(f)
|
||||
qb.addFilesTable(f, joinTypeInner)
|
||||
qb.addFoldersTable(f, joinTypeInner)
|
||||
},
|
||||
// don't use a subquery; join directly
|
||||
directJoin: true,
|
||||
|
|
@ -254,23 +267,23 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addSceneFilesTable(f *filterBuilder) {
|
||||
f.addLeftJoin(scenesFilesTable, "", "scenes_files.scene_id = scenes.id")
|
||||
func (qb *sceneFilterHandler) addSceneFilesTable(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, scenesFilesTable, "", "scenes_files.scene_id = scenes.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addFilesTable(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(fileTable, "", "scenes_files.file_id = files.id")
|
||||
func (qb *sceneFilterHandler) addFilesTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, fileTable, "", "scenes_files.file_id = files.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addFoldersTable(f *filterBuilder) {
|
||||
qb.addFilesTable(f)
|
||||
f.addLeftJoin(folderTable, "", "files.parent_folder_id = folders.id")
|
||||
func (qb *sceneFilterHandler) addFoldersTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addFilesTable(f, joinType)
|
||||
f.addJoin(joinType, folderTable, "", "files.parent_folder_id = folders.id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) addVideoFilesTable(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(videoFileTable, "", "video_files.file_id = scenes_files.file_id")
|
||||
func (qb *sceneFilterHandler) addVideoFilesTable(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinType)
|
||||
f.addJoin(joinType, videoFileTable, "", "video_files.file_id = scenes_files.file_id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) playCountCriterionHandler(count *models.IntCriterionInput) criterionHandlerFunc {
|
||||
|
|
@ -318,7 +331,7 @@ func (qb *sceneFilterHandler) duplicatedCriterionHandler(duplicatedFilter *model
|
|||
|
||||
// Handle explicit fields
|
||||
if duplicatedFilter.Phash != nil {
|
||||
qb.addSceneFilesTable(f)
|
||||
qb.addSceneFilesTable(f, joinTypeInner)
|
||||
qb.applyPhashDuplication(f, *duplicatedFilter.Phash)
|
||||
}
|
||||
|
||||
|
|
@ -368,11 +381,15 @@ func (qb *sceneFilterHandler) applyURLDuplication(f *filterBuilder, duplicated b
|
|||
f.addInnerJoin("(SELECT scene_id FROM scene_urls INNER JOIN (SELECT url FROM scene_urls GROUP BY url HAVING COUNT(DISTINCT scene_id) "+v+" 1) dupes ON scene_urls.url = dupes.url)", "scurl", "scenes.id = scurl.scene_id")
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
func (qb *sceneFilterHandler) codecCriterionHandler(codec *models.StringCriterionInput, codecColumn string, addJoinFn func(f *filterBuilder, joinType joinType)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if codec != nil {
|
||||
if addJoinFn != nil {
|
||||
addJoinFn(f)
|
||||
joinType := joinTypeInner
|
||||
if codec.Modifier == models.CriterionModifierIsNull {
|
||||
joinType = joinTypeLeft
|
||||
}
|
||||
addJoinFn(f, joinType)
|
||||
}
|
||||
|
||||
stringCriterionHandler(codec, codecColumn)(ctx, f)
|
||||
|
|
@ -398,29 +415,29 @@ func (qb *sceneFilterHandler) isMissingCriterionHandler(isMissing *string) crite
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
scenesURLsTableMgr.join(f, "", "scenes.id")
|
||||
scenesURLsTableMgr.leftJoin(f, "", "scenes.id")
|
||||
f.addWhere("scene_urls.url IS NULL")
|
||||
case "galleries":
|
||||
sceneRepository.galleries.join(f, "galleries_join", "scenes.id")
|
||||
sceneRepository.galleries.leftJoin(f, "galleries_join", "scenes.id")
|
||||
f.addWhere("galleries_join.scene_id IS NULL")
|
||||
case "studio":
|
||||
f.addWhere("scenes.studio_id IS NULL")
|
||||
case "movie", "group":
|
||||
sceneRepository.groups.join(f, "groups_join", "scenes.id")
|
||||
sceneRepository.groups.leftJoin(f, "groups_join", "scenes.id")
|
||||
f.addWhere("groups_join.scene_id IS NULL")
|
||||
case "performers":
|
||||
sceneRepository.performers.join(f, "performers_join", "scenes.id")
|
||||
sceneRepository.performers.leftJoin(f, "performers_join", "scenes.id")
|
||||
f.addWhere("performers_join.scene_id IS NULL")
|
||||
case "date":
|
||||
f.addWhere(`scenes.date IS NULL OR scenes.date IS ""`)
|
||||
case "tags":
|
||||
sceneRepository.tags.join(f, "tags_join", "scenes.id")
|
||||
sceneRepository.tags.leftJoin(f, "tags_join", "scenes.id")
|
||||
f.addWhere("tags_join.scene_id IS NULL")
|
||||
case "stash_id":
|
||||
sceneRepository.stashIDs.join(f, "scene_stash_ids", "scenes.id")
|
||||
sceneRepository.stashIDs.leftJoin(f, "scene_stash_ids", "scenes.id")
|
||||
f.addWhere("scene_stash_ids.scene_id IS NULL")
|
||||
case "phash":
|
||||
qb.addSceneFilesTable(f)
|
||||
qb.addSceneFilesTable(f, joinTypeLeft)
|
||||
f.addLeftJoin(fingerprintTable, "fingerprints_phash", "scenes_files.file_id = fingerprints_phash.file_id AND fingerprints_phash.type = 'phash'")
|
||||
f.addWhere("fingerprints_phash.fingerprint IS NULL")
|
||||
case "cover":
|
||||
|
|
@ -444,15 +461,15 @@ func (qb *sceneFilterHandler) urlsCriterionHandler(url *models.StringCriterionIn
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: scenesURLsTable,
|
||||
stringColumn: sceneURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
scenesURLsTableMgr.join(f, "", "scenes.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
scenesURLsTableMgr.join(f, joinType, "", "scenes.id")
|
||||
},
|
||||
}
|
||||
|
||||
return h.handler(url)
|
||||
}
|
||||
|
||||
func (qb *sceneFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder)) multiCriterionHandlerBuilder {
|
||||
func (qb *sceneFilterHandler) getMultiCriterionHandlerBuilder(foreignTable, joinTable, foreignFK string, addJoinsFunc func(f *filterBuilder, joinType joinType)) multiCriterionHandlerBuilder {
|
||||
return multiCriterionHandlerBuilder{
|
||||
primaryTable: sceneTable,
|
||||
foreignTable: foreignTable,
|
||||
|
|
@ -469,9 +486,9 @@ func (qb *sceneFilterHandler) captionCriterionHandler(captions *models.StringCri
|
|||
primaryFK: sceneIDColumn,
|
||||
joinTable: videoCaptionsTable,
|
||||
stringColumn: captionCodeColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
qb.addSceneFilesTable(f)
|
||||
f.addLeftJoin(videoCaptionsTable, "", "video_captions.file_id = scenes_files.file_id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
qb.addSceneFilesTable(f, joinTypeLeft)
|
||||
f.addJoin(joinType, videoCaptionsTable, "", "video_captions.file_id = scenes_files.file_id")
|
||||
},
|
||||
excludeHandler: func(f *filterBuilder, criterion *models.StringCriterionInput) {
|
||||
excludeClause := `scenes.id NOT IN (
|
||||
|
|
@ -531,8 +548,8 @@ func (qb *sceneFilterHandler) performersCriterionHandler(performers *models.Mult
|
|||
primaryFK: sceneIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
sceneRepository.performers.join(f, "performers_join", "scenes.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.performers.join(f, joinType, "performers_join", "scenes.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -587,9 +604,9 @@ func (qb *sceneFilterHandler) performerAgeCriterionHandler(performerAge *models.
|
|||
|
||||
// legacy handler
|
||||
func (qb *sceneFilterHandler) moviesCriterionHandler(movies *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
sceneRepository.groups.join(f, "", "scenes.id")
|
||||
f.addLeftJoin("groups", "", "groups_scenes.group_id = groups.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.groups.leftJoin(f, "", "scenes.id")
|
||||
f.addJoin(joinType, "groups", "", "groups_scenes.group_id = groups.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(groupTable, groupsScenesTable, "group_id", addJoinsFunc)
|
||||
return h.handler(movies)
|
||||
|
|
@ -613,9 +630,9 @@ func (qb *sceneFilterHandler) groupsCriterionHandler(groups *models.Hierarchical
|
|||
}
|
||||
|
||||
func (qb *sceneFilterHandler) galleriesCriterionHandler(galleries *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
sceneRepository.galleries.join(f, "", "scenes.id")
|
||||
f.addLeftJoin("galleries", "", "scenes_galleries.gallery_id = galleries.id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
sceneRepository.galleries.leftJoin(f, "", "scenes.id")
|
||||
f.addJoin(joinType, "galleries", "", "scenes_galleries.gallery_id = galleries.id")
|
||||
}
|
||||
h := qb.getMultiCriterionHandlerBuilder(galleryTable, scenesGalleriesTable, "gallery_id", addJoinsFunc)
|
||||
return h.handler(galleries)
|
||||
|
|
|
|||
|
|
@ -173,8 +173,8 @@ func (qb *sceneMarkerFilterHandler) performersCriterionHandler(performers *model
|
|||
primaryFK: sceneIDColumn,
|
||||
foreignFK: performerIDColumn,
|
||||
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
f.addLeftJoin(performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, performersScenesTable, "performers_join", "performers_join.scene_id = scene_markers.scene_id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -191,8 +191,8 @@ func (qb *sceneMarkerFilterHandler) performersCriterionHandler(performers *model
|
|||
}
|
||||
|
||||
func (qb *sceneMarkerFilterHandler) scenesCriterionHandler(scenes *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addLeftJoin(sceneTable, "markers_scenes", "markers_scenes.id = scene_markers.scene_id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, sceneTable, "markers_scenes", "markers_scenes.id = scene_markers.scene_id")
|
||||
}
|
||||
h := multiCriterionHandlerBuilder{
|
||||
primaryTable: sceneMarkerTable,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func (qb *studioFilterHandler) criterionHandler() criterionHandler {
|
|||
|
||||
criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if studioFilter.StashID != nil {
|
||||
studioRepository.stashIDs.join(f, "studio_stash_ids", "studios.id")
|
||||
studioRepository.stashIDs.leftJoin(f, "studio_stash_ids", "studios.id")
|
||||
stringCriterionHandler(studioFilter.StashID, "studio_stash_ids.stash_id")(ctx, f)
|
||||
}
|
||||
}),
|
||||
|
|
@ -143,15 +143,15 @@ func (qb *studioFilterHandler) isMissingCriterionHandler(isMissing *string) crit
|
|||
if isMissing != nil && *isMissing != "" {
|
||||
switch *isMissing {
|
||||
case "url":
|
||||
studiosURLsTableMgr.join(f, "", "studios.id")
|
||||
studiosURLsTableMgr.leftJoin(f, "", "studios.id")
|
||||
f.addWhere("studio_urls.url IS NULL")
|
||||
case "image":
|
||||
f.addWhere("studios.image_blob IS NULL")
|
||||
case "stash_id":
|
||||
studioRepository.stashIDs.join(f, "studio_stash_ids", "studios.id")
|
||||
studioRepository.stashIDs.leftJoin(f, "studio_stash_ids", "studios.id")
|
||||
f.addWhere("studio_stash_ids.studio_id IS NULL")
|
||||
case "aliases":
|
||||
studiosAliasesTableMgr.join(f, "", "studios.id")
|
||||
studiosAliasesTableMgr.leftJoin(f, "", "studios.id")
|
||||
f.addWhere("studio_aliases.alias IS NULL")
|
||||
case "tags":
|
||||
f.addLeftJoin(studiosTagsTable, "tags_join", "tags_join.studio_id = studios.id")
|
||||
|
|
@ -224,8 +224,8 @@ func (qb *studioFilterHandler) tagCountCriterionHandler(tagCount *models.IntCrit
|
|||
}
|
||||
|
||||
func (qb *studioFilterHandler) parentCriterionHandler(parents *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addLeftJoin("studios", "parent_studio", "parent_studio.id = studios.parent_id")
|
||||
addJoinsFunc := func(f *filterBuilder, joinType joinType) {
|
||||
f.addJoin(joinType, "studios", "parent_studio", "parent_studio.id = studios.parent_id")
|
||||
}
|
||||
h := multiCriterionHandlerBuilder{
|
||||
primaryTable: studioTable,
|
||||
|
|
@ -244,8 +244,8 @@ func (qb *studioFilterHandler) aliasCriterionHandler(alias *models.StringCriteri
|
|||
primaryFK: studioIDColumn,
|
||||
joinTable: studioAliasesTable,
|
||||
stringColumn: studioAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
studiosAliasesTableMgr.join(f, "", "studios.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
studiosAliasesTableMgr.join(f, joinType, "", "studios.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -258,8 +258,8 @@ func (qb *studioFilterHandler) urlsCriterionHandler(url *models.StringCriterionI
|
|||
primaryFK: studioIDColumn,
|
||||
joinTable: studioURLsTable,
|
||||
stringColumn: studioURLColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
studiosURLsTableMgr.join(f, "", "studios.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
studiosURLsTableMgr.join(f, joinType, "", "studios.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,22 @@ func (t *table) destroy(ctx context.Context, ids []int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *table) join(j joiner, as string, parentIDCol string) {
|
||||
func (t *table) join(j joiner, jt joinType, as string, parentIDCol string) {
|
||||
tableName := t.table.GetTable()
|
||||
tt := tableName
|
||||
if as != "" {
|
||||
tt = as
|
||||
}
|
||||
|
||||
fn := j.addInnerJoin
|
||||
if jt == joinTypeLeft {
|
||||
fn = j.addLeftJoin
|
||||
}
|
||||
|
||||
fn(tableName, as, fmt.Sprintf("%s.%s = %s", tt, t.idColumn.GetCol(), parentIDCol))
|
||||
}
|
||||
|
||||
func (t *table) leftJoin(j joiner, as string, parentIDCol string) {
|
||||
tableName := t.table.GetTable()
|
||||
tt := tableName
|
||||
if as != "" {
|
||||
|
|
|
|||
|
|
@ -184,8 +184,8 @@ func (qb *tagFilterHandler) aliasCriterionHandler(alias *models.StringCriterionI
|
|||
primaryFK: tagIDColumn,
|
||||
joinTable: tagAliasesTable,
|
||||
stringColumn: tagAliasColumn,
|
||||
addJoinTable: func(f *filterBuilder) {
|
||||
tagRepository.aliases.join(f, "", "tags.id")
|
||||
addJoinTable: func(f *filterBuilder, joinType joinType) {
|
||||
tagRepository.aliases.join(f, joinType, "", "tags.id")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -199,10 +199,10 @@ func (qb *tagFilterHandler) isMissingCriterionHandler(isMissing *string) criteri
|
|||
case "image":
|
||||
f.addWhere("tags.image_blob IS NULL")
|
||||
case "aliases":
|
||||
tagRepository.aliases.join(f, "", "tags.id")
|
||||
tagRepository.aliases.leftJoin(f, "", "tags.id")
|
||||
f.addWhere("tag_aliases.alias IS NULL")
|
||||
case "stash_id":
|
||||
tagRepository.stashIDs.join(f, "tag_stash_ids", "tags.id")
|
||||
tagRepository.stashIDs.leftJoin(f, "tag_stash_ids", "tags.id")
|
||||
f.addWhere("tag_stash_ids.tag_id IS NULL")
|
||||
default:
|
||||
if err := validateIsMissing(*isMissing, []string{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
{
|
||||
"name": "stash",
|
||||
"private": true,
|
||||
"homepage": "./",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
|
||||
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
|
|
@ -23,39 +25,39 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@ant-design/react-slick": "^1.0.0",
|
||||
"@apollo/client": "^3.8.10",
|
||||
"@formatjs/intl-getcanonicallocales": "^2.0.5",
|
||||
"@formatjs/intl-locale": "^3.0.11",
|
||||
"@formatjs/intl-numberformat": "^8.3.3",
|
||||
"@formatjs/intl-pluralrules": "^5.1.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||
"@apollo/client": "3.14",
|
||||
"@blaineam/videojs-vr": "^3.1.1",
|
||||
"@formatjs/intl-getcanonicallocales": "^3.2.2",
|
||||
"@formatjs/intl-locale": "^5.3.1",
|
||||
"@formatjs/intl-numberformat": "^8.15.6",
|
||||
"@formatjs/intl-pluralrules": "^6.3.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.6",
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@silvermine/videojs-airplay": "^1.2.0",
|
||||
"@silvermine/videojs-chromecast": "^1.4.1",
|
||||
"@silvermine/videojs-airplay": "^1.3.0",
|
||||
"@silvermine/videojs-chromecast": "^1.5.0",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"apollo-upload-client": "^18.0.1",
|
||||
"apollo-upload-client": "18",
|
||||
"base64-blob": "^1.4.1",
|
||||
"bootstrap": "^4.6.2",
|
||||
"classnames": "^2.3.2",
|
||||
"classnames": "^2.5.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"event-target-polyfill": "^0.0.4",
|
||||
"flag-icons": "^6.6.6",
|
||||
"flag-icons": "^7.5.0",
|
||||
"flexbin": "^0.2.0",
|
||||
"formik": "^2.4.5",
|
||||
"formik": "^2.4.9",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.14.3",
|
||||
"i18n-iso-countries": "^7.5.0",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"localforage": "^1.10.0",
|
||||
"lodash-es": "^4.17.23",
|
||||
"lodash-es": "^4.18.1",
|
||||
"moment": "^2.30.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mousetrap-pause": "^1.0.0",
|
||||
"normalize-url": "^4.5.1",
|
||||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^1.6.6",
|
||||
"react-datepicker": "^4.10.0",
|
||||
|
|
@ -71,21 +73,19 @@
|
|||
"remark-gfm": "^1.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"slick-carousel": "^1.8.1",
|
||||
"string.prototype.replaceall": "^1.0.7",
|
||||
"thehandy": "^1.0.3",
|
||||
"ua-parser-js": "^1.0.34",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"video.js": "^7.21.3",
|
||||
"thehandy": "^1.1.0",
|
||||
"ua-parser-js": "^2.0.9",
|
||||
"universal-cookie": "^8.1.0",
|
||||
"video.js": "^7.21.7",
|
||||
"videojs-abloop": "^1.2.0",
|
||||
"videojs-contrib-dash": "^5.1.1",
|
||||
"videojs-mobile-ui": "^0.8.0",
|
||||
"videojs-seek-buttons": "^3.0.1",
|
||||
"videojs-vr": "1.8.0",
|
||||
"videojs-vtt.js": "^0.15.4",
|
||||
"yup": "^1.3.2"
|
||||
"videojs-vtt.js": "^0.15.5",
|
||||
"yup": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/time": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
|
|
@ -93,45 +93,44 @@
|
|||
"@graphql-codegen/typescript-react-apollo": "^4.1.0",
|
||||
"@types/apollo-upload-client": "^18.0.0",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/dom-screen-wake-lock": "^1.0.3",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/mousetrap": "^1.6.11",
|
||||
"@types/node": "^18.13.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/mousetrap": "^1.6.15",
|
||||
"@types/node": "^20.19.37",
|
||||
"@types/react": "^17.0.53",
|
||||
"@types/react-datepicker": "^4.10.0",
|
||||
"@types/react-dom": "^17.0.19",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/react-router-bootstrap": "^0.24.5",
|
||||
"@types/react-router-hash-link": "^2.4.5",
|
||||
"@types/three": "^0.154.0",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/video.js": "^7.3.51",
|
||||
"@types/videojs-mobile-ui": "^0.8.0",
|
||||
"@types/videojs-seek-buttons": "^2.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"@vitejs/plugin-legacy": "^5.4.3",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"eslint": "^8.34.0",
|
||||
"@types/three": "^0.183.1",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"@types/videojs-mobile-ui": "^0.8.3",
|
||||
"@types/videojs-seek-buttons": "^2.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitejs/plugin-react": "^5.2.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"eslint-config-prettier": "^8.10.2",
|
||||
"eslint-plugin-deprecation": "^3.0.0",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"extract-react-intl-messages": "^4.1.1",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.4",
|
||||
"sass": "^1.58.1",
|
||||
"stylelint": "^15.10.1",
|
||||
"stylelint-order": "^6.0.2",
|
||||
"terser": "^5.9.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "~4.8.4",
|
||||
"vite": "^5.4.21",
|
||||
"postcss": "^8.5.8",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"prettier": "^2.8.8",
|
||||
"sass": "^1.98.0",
|
||||
"stylelint": "^17.6.0",
|
||||
"stylelint-order": "^8.1.1",
|
||||
"terser": "^5.46.1",
|
||||
"ts-node": "~10.9.2",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.3.2",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-tsconfig-paths": "^4.0.5"
|
||||
"vite-tsconfig-paths": "^6.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,3 +2,7 @@ onlyBuiltDependencies:
|
|||
- '@parcel/watcher'
|
||||
- core-js
|
||||
- esbuild
|
||||
overrides:
|
||||
"yaml@1.10.2": "~1.10.3"
|
||||
"brace-expansion@1.1.12": "~1.1.13"
|
||||
"@xmldom/xmldom@0.8.12": "~0.8.13"
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
declare module "string.prototype.replaceall" {
|
||||
function replaceAll(
|
||||
searchValue: string | RegExp,
|
||||
replaceValue: string
|
||||
): string;
|
||||
function replaceAll(
|
||||
searchValue: string | RegExp,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
replacer: (substring: string, ...args: any[]) => string
|
||||
): string;
|
||||
|
||||
namespace replaceAll {
|
||||
function getPolyfill(): typeof replaceAll;
|
||||
function implementation(): typeof replaceAll;
|
||||
function shim(): void;
|
||||
}
|
||||
|
||||
export default replaceAll;
|
||||
}
|
||||
31
ui/v2.5/src/@types/videojs-contrib-dash.d.ts
vendored
31
ui/v2.5/src/@types/videojs-contrib-dash.d.ts
vendored
|
|
@ -1,31 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
declare module "videojs-contrib-dash" {
|
||||
class Html5DashJS {
|
||||
/**
|
||||
* Get a list of hooks for a specific lifecycle.
|
||||
*
|
||||
* @param type the lifecycle to get hooks from
|
||||
* @param hook optionally add a hook to the lifecycle
|
||||
* @return an array of hooks or empty if none
|
||||
*/
|
||||
static hooks(type: string, hook: Function | Function[]): Function[];
|
||||
|
||||
/**
|
||||
* Add a function hook to a specific dash lifecycle.
|
||||
*
|
||||
* @param type the lifecycle to hook the function to
|
||||
* @param hook the function or array of functions to attach
|
||||
*/
|
||||
static hook(type: string, hook: Function | Function[]): void;
|
||||
|
||||
/**
|
||||
* Remove a hook from a specific dash lifecycle.
|
||||
*
|
||||
* @param type the lifecycle that the function hooked to
|
||||
* @param hook the hooked function to remove
|
||||
* @return true if the function was removed, false if not found
|
||||
*/
|
||||
static removeHook(type: string, hook: Function): boolean;
|
||||
}
|
||||
}
|
||||
113
ui/v2.5/src/@types/videojs-vr.d.ts
vendored
113
ui/v2.5/src/@types/videojs-vr.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
declare module "videojs-vr" {
|
||||
declare module "@blaineam/videojs-vr" {
|
||||
import videojs from "video.js";
|
||||
// we don't want to depend on THREE.js directly, these are just typedefs for videojs-vr
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
|
|
@ -36,61 +36,50 @@ declare module "videojs-vr" {
|
|||
// Used for Equi-Angular Cubemap videos
|
||||
| "EAC"
|
||||
// Used for side-by-side Equi-Angular Cubemap videos
|
||||
| "EAC_LR";
|
||||
| "EAC_LR"
|
||||
// flat screen side-by-side
|
||||
| "SBS_MONO";
|
||||
|
||||
interface mediaItem {
|
||||
title: string;
|
||||
thumbnail: string;
|
||||
url: string;
|
||||
duration?: number;
|
||||
}
|
||||
type mediaItems = mediaItem[];
|
||||
|
||||
type orientationOffset = {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
};
|
||||
|
||||
// options are taken verbaitum from the README
|
||||
interface Options {
|
||||
/**
|
||||
* Force the cardboard button to display on all devices even if we don't think they support it.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceCardboard?: boolean;
|
||||
// Projection mode
|
||||
projection?: ProjectionType; // see ProjectionType
|
||||
sphereDetails?: number; // Sphere mesh detail (higher = smoother)
|
||||
|
||||
/**
|
||||
* Whether motion/gyro controls should be enabled.
|
||||
*
|
||||
* @default true on iOS and Android
|
||||
*/
|
||||
motionControls?: boolean;
|
||||
// VR HUD options
|
||||
enableVRHud?: boolean; // Enable in-VR controls
|
||||
enableVRGallery?: boolean; // Enable in-VR video gallery
|
||||
showHUDOnStart?: boolean; // Show HUD when entering VR
|
||||
hudAutoHideDelay?: number; // Auto-hide HUD after ms (0 to disable)
|
||||
hudDistance?: number; // Distance of HUD from viewer
|
||||
hudHeight?: number; // Height of HUD
|
||||
hudScale?: number; // Scale of HUD elements
|
||||
|
||||
/**
|
||||
* Defines the projection type.
|
||||
*
|
||||
* @default "AUTO"
|
||||
*/
|
||||
projection?: ProjectionType;
|
||||
// Behavior options
|
||||
forceCardboard?: boolean; // Force cardboard button on all devices
|
||||
motionControls?: boolean; // Enable gyroscope/device orientation
|
||||
disableTogglePlay?: boolean; // Disable click-to-play
|
||||
|
||||
/**
|
||||
* This alters the number of segments in the spherical mesh onto which equirectangular videos are projected.
|
||||
* The default is 32 but in some circumstances you may notice artifacts and need to increase this number.
|
||||
*
|
||||
* @default 32
|
||||
*/
|
||||
sphereDetail?: number;
|
||||
// Spatial audio (requires Omnitone library)
|
||||
omnitone?: Object; // Pass Omnitone library object
|
||||
omnitoneOptions?: Record<string, unknown>; // Omnitone configuration
|
||||
|
||||
/**
|
||||
* Enable debug logging for this plugin
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
debug?: boolean;
|
||||
|
||||
/**
|
||||
* Use this property to pass the Omnitone library object to the plugin. Please be aware of, the Omnitone library is not included in the build files.
|
||||
*/
|
||||
omnitone?: object;
|
||||
|
||||
/**
|
||||
* Default options for the Omnitone library. Please check available options on https://github.com/GoogleChrome/omnitone
|
||||
*/
|
||||
omnitoneOptions?: object;
|
||||
|
||||
/**
|
||||
* Feature to disable the togglePlay manually. This functionality is useful in live events so that users cannot stop the live, but still have a controlBar available.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disableTogglePlay?: boolean;
|
||||
// Media gallery items
|
||||
mediaItems?: mediaItems; // Array of media items for gallery
|
||||
}
|
||||
|
||||
interface PlayerMediaInfo {
|
||||
|
|
@ -106,11 +95,33 @@ declare module "videojs-vr" {
|
|||
init(): void;
|
||||
reset(): void;
|
||||
|
||||
cameraVector: THREE.Vector3;
|
||||
// VR HUD
|
||||
showHUD(): void; // Show the VR HUD
|
||||
hideHUD(): void; // Hide the VR HUD
|
||||
toggleHUD(): void; // Toggle HUD visibility
|
||||
|
||||
// VR Gallery
|
||||
showGallery(): void; // Show the gallery panel
|
||||
hideGallery(): void; // Hide the gallery panel
|
||||
toggleGallery(): void; // Toggle gallery visibility
|
||||
setGalleryItems(mediaItems): void; // Update gallery media items
|
||||
|
||||
// Favorite state
|
||||
setFavoriteState(boolean): void; // Set favorite button state
|
||||
getFavoriteState(): boolean; // Get current favorite state
|
||||
|
||||
// Orientation
|
||||
setOrientationOffset(orientationOffset): void; // Tilt view
|
||||
resetOrientationOffset(): void; // Reset to default orientation
|
||||
recenter(): void; // Recenter VR view
|
||||
|
||||
// Status
|
||||
isPresenting(): boolean; // Check if currently in VR mode
|
||||
|
||||
camera: THREE.Camera;
|
||||
scene: THREE.Scene;
|
||||
renderer: THREE.Renderer;
|
||||
cameraVector: THREE.Vector3;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
interface IProps {
|
||||
gallery: Partial<GQL.GalleryDataFragment>;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
interface IGroupEditPanel {
|
||||
group: Partial<GQL.GroupDataFragment>;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
interface IProps {
|
||||
image: GQL.ImageDataFragment;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
CriterionModifier,
|
||||
CustomFieldCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { ModifierSelect } from "../ModifierSelect";
|
||||
import { useIntl } from "react-intl";
|
||||
import { Icon } from "src/components/Shared/Icon";
|
||||
|
|
|
|||
|
|
@ -514,8 +514,8 @@ ul.selectable-list {
|
|||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
// to prevent unnecessary vertical scrollbar
|
||||
padding-bottom: 0.15rem;
|
||||
padding-inline-start: 0;
|
||||
padding-bottom: 0.15rem;
|
||||
|
||||
.modifier-object {
|
||||
font-style: italic;
|
||||
|
|
@ -621,8 +621,8 @@ ul.selectable-list {
|
|||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
// to prevent unnecessary vertical scrollbar
|
||||
padding-bottom: 0.15rem;
|
||||
padding-inline-start: 0;
|
||||
padding-bottom: 0.15rem;
|
||||
|
||||
.modifier-object {
|
||||
font-style: italic;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
const isScraper = (
|
||||
scraper: GQL.Scraper | GQL.StashBox
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
import videojs, { VideoJsPlayer } from "video.js";
|
||||
import "videojs-vr";
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import "@blaineam/videojs-vr";
|
||||
// separate type import, otherwise typescript elides the above import
|
||||
// and the plugin does not get initialized
|
||||
import type { ProjectionType, Plugin as VideoJsVRPlugin } from "videojs-vr";
|
||||
import type {
|
||||
ProjectionType,
|
||||
Plugin as VideoJsVRPlugin,
|
||||
} from "@blaineam/videojs-vr";
|
||||
|
||||
export interface VRMenuOptions {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog"));
|
||||
const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal"));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { CollapseButton } from "./CollapseButton";
|
|||
import { DetailItem } from "./DetailItem";
|
||||
import { Button, Col, Form, FormGroup, InputGroup, Row } from "react-bootstrap";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import { Icon } from "./Icon";
|
||||
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import cx from "classnames";
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
interface IStudioEditPanel {
|
||||
studio: Partial<GQL.StudioDataFragment>;
|
||||
|
|
|
|||
|
|
@ -303,8 +303,6 @@ export const TaggerContext: React.FC = ({ children }) => {
|
|||
|
||||
if (results.error) {
|
||||
newResult = { error: results.error.message };
|
||||
} else if (results.errors) {
|
||||
newResult = { error: results.errors.toString() };
|
||||
} else {
|
||||
newResult = {
|
||||
results: results.data.scrapeSingleScene.map((r) => ({
|
||||
|
|
@ -339,8 +337,6 @@ export const TaggerContext: React.FC = ({ children }) => {
|
|||
|
||||
if (results.error) {
|
||||
newResult = { error: results.error.message };
|
||||
} else if (results.errors) {
|
||||
newResult = { error: results.errors.toString() };
|
||||
} else {
|
||||
newResult = {
|
||||
results: results.data.scrapeSingleScene.map((r) => ({
|
||||
|
|
@ -401,8 +397,6 @@ export const TaggerContext: React.FC = ({ children }) => {
|
|||
|
||||
if (results.error) {
|
||||
setMultiError(results.error.message);
|
||||
} else if (results.errors) {
|
||||
setMultiError(results.errors.toString());
|
||||
} else {
|
||||
const newSearchResults = { ...searchResults };
|
||||
sceneIDs.forEach((sceneID, index) => {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
CustomFieldsInput,
|
||||
formatCustomFieldInput,
|
||||
} from "src/components/Shared/CustomFields";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
interface ITagEditPanel {
|
||||
tag: Partial<GQL.TagDataFragment>;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import {
|
||||
ApolloClient,
|
||||
InMemoryCache,
|
||||
split,
|
||||
from,
|
||||
ApolloLink,
|
||||
ServerError,
|
||||
TypePolicies,
|
||||
} from "@apollo/client";
|
||||
|
|
@ -171,7 +170,7 @@ Please disable it on the server and refresh the page.`);
|
|||
}
|
||||
});
|
||||
|
||||
const splitLink = split(
|
||||
const splitLink = ApolloLink.split(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
|
|
@ -183,7 +182,7 @@ Please disable it on the server and refresh the page.`);
|
|||
httpLink
|
||||
);
|
||||
|
||||
const link = from([errorLink, splitLink]);
|
||||
const link = ApolloLink.from([errorLink, splitLink]);
|
||||
|
||||
const cache = new InMemoryCache({
|
||||
typePolicies,
|
||||
|
|
|
|||
|
|
@ -11,32 +11,32 @@ $sidebar-width: 250px;
|
|||
@import "styles/range";
|
||||
@import "styles/scrollbars";
|
||||
@import "sfw-mode.scss";
|
||||
@import "src/components/Changelog/styles.scss";
|
||||
@import "src/components/Galleries/styles.scss";
|
||||
@import "src/components/Help/styles.scss";
|
||||
@import "src/components/Images/styles.scss";
|
||||
@import "src/components/List/styles.scss";
|
||||
@import "src/components/Groups/styles.scss";
|
||||
@import "src/components/Performers/styles.scss";
|
||||
@import "src/components/FrontPage/styles.scss";
|
||||
@import "src/components/Scenes/styles.scss";
|
||||
@import "src/components/SceneDuplicateChecker/styles.scss";
|
||||
@import "src/components/SceneFilenameParser/styles.scss";
|
||||
@import "src/components/ScenePlayer/styles.scss";
|
||||
@import "src/components/Settings/styles.scss";
|
||||
@import "src/components/Setup/styles.scss";
|
||||
@import "src/components/Studios/styles.scss";
|
||||
@import "src/components/Shared/styles.scss";
|
||||
@import "src/components/Shared/GridCard/styles.scss";
|
||||
@import "src/components/Shared/Rating/styles.scss";
|
||||
@import "src/components/Shared/PackageManager/styles.scss";
|
||||
@import "src/components/Tags/styles.scss";
|
||||
@import "src/components/Wall/styles.scss";
|
||||
@import "src/components/Tagger/styles.scss";
|
||||
@import "src/hooks/Lightbox/lightbox.scss";
|
||||
@import "src/hooks/Interactive/interactive.scss";
|
||||
@import "src/components/Dialogs/IdentifyDialog/styles.scss";
|
||||
@import "src/components/Dialogs/styles.scss";
|
||||
@import "components/Changelog/styles.scss";
|
||||
@import "components/Galleries/styles.scss";
|
||||
@import "components/Help/styles.scss";
|
||||
@import "components/Images/styles.scss";
|
||||
@import "components/List/styles.scss";
|
||||
@import "components/Groups/styles.scss";
|
||||
@import "components/Performers/styles.scss";
|
||||
@import "components/FrontPage/styles.scss";
|
||||
@import "components/Scenes/styles.scss";
|
||||
@import "components/SceneDuplicateChecker/styles.scss";
|
||||
@import "components/SceneFilenameParser/styles.scss";
|
||||
@import "components/ScenePlayer/styles.scss";
|
||||
@import "components/Settings/styles.scss";
|
||||
@import "components/Setup/styles.scss";
|
||||
@import "components/Studios/styles.scss";
|
||||
@import "components/Shared/styles.scss";
|
||||
@import "components/Shared/GridCard/styles.scss";
|
||||
@import "components/Shared/Rating/styles.scss";
|
||||
@import "components/Shared/PackageManager/styles.scss";
|
||||
@import "components/Tags/styles.scss";
|
||||
@import "components/Wall/styles.scss";
|
||||
@import "components/Tagger/styles.scss";
|
||||
@import "hooks/Lightbox/lightbox.scss";
|
||||
@import "hooks/Interactive/interactive.scss";
|
||||
@import "components/Dialogs/IdentifyDialog/styles.scss";
|
||||
@import "components/Dialogs/styles.scss";
|
||||
@import "flag-icons/css/flag-icons.min.css";
|
||||
|
||||
/* stylelint-disable */
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
CriterionModifier,
|
||||
CustomFieldCriterionInput,
|
||||
} from "src/core/generated-graphql";
|
||||
import { cloneDeep } from "@apollo/client/utilities";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
|
||||
function valueToString(value: unknown[] | undefined | null) {
|
||||
if (!value) return "";
|
||||
|
|
|
|||
|
|
@ -1,29 +1,26 @@
|
|||
import replaceAll from "string.prototype.replaceall";
|
||||
import { shouldPolyfill as shouldPolyfillCanonicalLocales } from "@formatjs/intl-getcanonicallocales/should-polyfill";
|
||||
import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/should-polyfill";
|
||||
import { shouldPolyfill as shouldPolyfillNumberformat } from "@formatjs/intl-numberformat/should-polyfill";
|
||||
import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/should-polyfill";
|
||||
/* eslint-disable import/extensions */
|
||||
import { shouldPolyfill as shouldPolyfillCanonicalLocales } from "@formatjs/intl-getcanonicallocales/should-polyfill.js";
|
||||
import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/should-polyfill.js";
|
||||
import { shouldPolyfill as shouldPolyfillNumberformat } from "@formatjs/intl-numberformat/should-polyfill.js";
|
||||
import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/should-polyfill.js";
|
||||
|
||||
// needed for older safari versions
|
||||
import "event-target-polyfill";
|
||||
|
||||
// Required for browsers older than August 2020ish. Can be removed at some point.
|
||||
replaceAll.shim();
|
||||
|
||||
async function checkPolyfills() {
|
||||
if (shouldPolyfillCanonicalLocales()) {
|
||||
await import("@formatjs/intl-getcanonicallocales/polyfill");
|
||||
await import("@formatjs/intl-getcanonicallocales/polyfill.js");
|
||||
}
|
||||
if (shouldPolyfillLocale()) {
|
||||
await import("@formatjs/intl-locale/polyfill");
|
||||
await import("@formatjs/intl-locale/polyfill.js");
|
||||
}
|
||||
if (shouldPolyfillNumberformat()) {
|
||||
await import("@formatjs/intl-numberformat/polyfill");
|
||||
await import("@formatjs/intl-numberformat/polyfill.js");
|
||||
await import("@formatjs/intl-numberformat/locale-data/en");
|
||||
await import("@formatjs/intl-numberformat/locale-data/en-GB");
|
||||
}
|
||||
if (shouldPolyfillPluralRules()) {
|
||||
await import("@formatjs/intl-pluralrules/polyfill");
|
||||
await import("@formatjs/intl-pluralrules/polyfill.js");
|
||||
await import("@formatjs/intl-pluralrules/locale-data/en");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ $textfield-bg: rgba(16, 22, 26, 0.3);
|
|||
$card-bg: #30404d;
|
||||
$card-cap-bg: rgba(#000, 0.03);
|
||||
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
|
||||
$red1: #a82a2a;
|
||||
$orange1: #a66321;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import UAParser from "ua-parser-js";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
|
||||
export function isPlatformUniquelyRenderedByApple() {
|
||||
// OS name on iPads show up as iOS or Max OS depending on the browser.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"isolatedModules": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"useDefineForClassFields": true,
|
||||
"types": ["vite/client", "dom-screen-wake-lock"]
|
||||
"types": ["vite/client"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import legacy from "@vitejs/plugin-legacy";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
|
||||
const nolegacy = process.env.VITE_APP_NOLEGACY === "true";
|
||||
const sourcemap = process.env.VITE_APP_SOURCEMAPS === "true";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
|
|
@ -24,10 +22,6 @@ export default defineConfig(() => {
|
|||
}),
|
||||
];
|
||||
|
||||
if (!nolegacy) {
|
||||
plugins = [...plugins, legacy()];
|
||||
}
|
||||
|
||||
return {
|
||||
base: "",
|
||||
build: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue