Compare commits

...

407 Commits

Author SHA1 Message Date
7dJx1qP
8e222ae387 Fix locale import missing translation properties (#2772)
* Fix locale import missing translation properties
2022-07-25 10:46:43 +10:00
WithoutPants
7f86509674 Update changelog 2022-07-22 17:34:39 +10:00
WithoutPants
4b7ef76321 Focus scene player after loading new scene (#2758)
* Focus scene player after loading new scene
2022-07-22 17:32:03 +10:00
WithoutPants
c21c334553 Fix % character in tag name causing UI crash (#2757)
* Fix % character in tag name causing UI crash
2022-07-22 17:29:16 +10:00
WithoutPants
db3138b33f Fix keyboard shortcuts not working after selecting (#2756)
* Fix keyboard shortcuts not working after selecting
2022-07-22 17:25:48 +10:00
WithoutPants
e532f9a683 Allow unauthenticated access to UI assets (#2755)
* Allow unauthenticated access to UI assets
2022-07-22 17:23:40 +10:00
peolic
b8262f5641 Fix non-default video stream from ffprobe result (#2752)
* Fix non-default video stream from ffprobe result
2022-07-22 17:21:39 +10:00
WithoutPants
6f7cc11b86 Fix incorrect image displayed in lightbox (#2754)
* Fix incorrect image displayed in lightbox
* Add changelog
2022-07-22 17:18:11 +10:00
WithoutPants
9ac5d3f3d9 Fix messages when adding/removing gallery image (#2748) 2022-07-18 11:07:17 +10:00
WithoutPants
11d5c7f386 Fix UI crash when scene has stash ids but no stash-box configured (#2749)
* Add error boundary to show navbar on error
* Fix crash when scene has stash-ids but no stash-box configured
2022-07-18 10:46:39 +10:00
WithoutPants
2427519100 Enable automated build for files-refactor (#2738) 2022-07-13 15:46:44 +10:00
DingDongSoLong4
91e3fcc7ff Fix lodash-es typos (#2724)
* Fix LocalForage not saving items
* Fix bulk update typos
2022-07-13 10:08:02 +10:00
kermieisinthehouse
4fa103dd08 Fix alphabetical css (#2733) 2022-07-09 21:51:39 -07:00
erri120
3a3a296995 Add nowrap to rating stars and overflow-wrap to title for mobile (#2731) 2022-07-09 21:23:17 -07:00
stash-translation-bot
351472f654 Translations update from Stash (#2690)
* Translated using Weblate (French)

Currently translated at 93.7% (768 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Added translation using Weblate (Thai)

* Translated using Weblate (Thai)

Currently translated at 5.2% (43 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/th/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Korean)

Currently translated at 99.3% (814 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ko/

* Translated using Weblate (Korean)

Currently translated at 99.8% (818 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ko/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/en_GB/

* Translated using Weblate (Romanian)

Currently translated at 45.6% (374 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ro/

Co-authored-by: - <adr.web@hotmail.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Mr.Kay <taksayir@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Barack Obama <starebod@gmail.com>
Co-authored-by: yc <yechan24680@gmail.com>
Co-authored-by: Bogdan <bogddan1337@pm.me>
2022-07-04 17:22:53 -07:00
bnkai
92acd78401 Fix name,path filter naming (#2712)
* Fix name,path filter naming

* * fmt-ui
2022-07-04 17:07:19 -07:00
Russell Harmon
813495c7f7 Set crossorigin use-credentials for manifest.json. (#2706)
This is necessary when running behind a password-protected reverse
proxy, as otherwise Chrome doesn't send HTTP basic auth credentials when
requesting this file.

See
https://stackoverflow.com/questions/6294622/html5-manifest-cache-behind-basic-auth
for more information.
2022-06-29 21:49:04 -07:00
WithoutPants
d32e375521 Treat edited values as scraped (#2702)
* Treat edited values as scraped
* Fix scrape selection on change
2022-06-30 09:25:13 +10:00
DingDongSoLong4
b3bc5b999f Fix saved filter search (#2698)
* Fix saved filter search excluding filters with uppercase characters
2022-06-27 11:38:13 +10:00
WithoutPants
f3e6cb7b0e Fix proxy prefix replacement (#2694) 2022-06-27 09:53:40 +10:00
WithoutPants
5e7bf1c2d7 Update changelog 2022-06-23 11:18:20 +10:00
DingDongSoLong4
63e1bbf35d UI and filter fixes (#2686)
* Use primitive string in recommendation row props
* Use unique keys in recommendation rows

The keys for the cards used while loading clash with the ids of the actual cards, causing a list unique key warning.

* List filter alignment tweaks
* Rework list hook filtering
* Internationalise checksum correctly
2022-06-22 15:45:47 +10:00
WithoutPants
3b4b20e9b2 React code splitting (#2603)
* Code split using react lazy
* Split locales
* Move to lodash-es
* Import individual icons
2022-06-22 14:41:31 +10:00
stash-translation-bot
33b68b4464 Translations update from Stash (#2653)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (818 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (817 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (817 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (French)

Currently translated at 93.7% (767 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (German)

Currently translated at 100.0% (818 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Italian)

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Polish)

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.7% (817 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Spanish)

Currently translated at 98.1% (804 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (French)

Currently translated at 93.5% (766 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 93.7% (768 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (German)

Currently translated at 100.0% (819 of 819 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: GeneX <gxetrek@gmail.com>
Co-authored-by: ponei <poneialt@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: - <adr.web@hotmail.fr>
Co-authored-by: Phasetime <phasetime@protonmail.com>
Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Alex <majocorsi@gmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: rather not <big_brother_is_watching4u@protonmail.com>
2022-06-21 19:02:34 -07:00
iampabber
1ab02a1748 Use hotkeys '[' and ']' to scrub video player forwards and backwards by 10% of the scene (#2678)
* Use hotkeys '[' and ']' to scrub video player forwards and backwards by 10% of the scene
* Don't loop back to beginning
* Add manual keybind entry

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-06-22 11:33:17 +10:00
bnkai
6cfb7fe79d Fix Synopsis json string in movie jsonschema (#2664)
* Fix Synopsis json string in movie jsonschema
* backwards compatible movie synopsis import
2022-06-22 10:59:39 +10:00
TgSeed
abd76f7e58 Fix/ffprobe unmarshalling error (#2685)
Fix/ffprobe unmarshalling error
2022-06-22 10:49:14 +10:00
WithoutPants
733ca2aa6f Run build on all pull requests 2022-06-19 11:54:00 +10:00
WithoutPants
c2f7617952 Separate filter buttons from query field (#2668) 2022-06-15 11:51:05 +10:00
WithoutPants
75a795b2e6 Add tag recommendation row (#2673) 2022-06-15 11:23:39 +10:00
WithoutPants
6029918d22 Fix ui config conversion (#2672) 2022-06-15 10:31:09 +10:00
dumdum7
582ffa1420 Don't switch to landscape for portrait videos (#2665) 2022-06-14 11:19:12 +10:00
WithoutPants
900ba936a9 Update changelog 2022-06-14 10:41:35 +10:00
WithoutPants
a2e8f69028 Fix scene player event handler initialisation (#2656) 2022-06-14 10:39:46 +10:00
CJ
9264c15540 Customize recommendations (#2592)
* refactored common code in recommendation row
* Implement front page options in config
* Allow customisation from front page
* Rename recommendations to front page
* Add generic UI settings
* Support adding premade filters

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-06-14 10:34:04 +10:00
WithoutPants
ff724d82cc Don't trim extension for folders when auto-tagging galleries (#2658) 2022-06-08 09:02:11 +10:00
WithoutPants
456e9409e0 Update scene screenshot in edit panel (#2657) 2022-06-08 08:58:59 +10:00
WithoutPants
5e455d6530 Add alias for Laos (#2655) 2022-06-08 08:58:42 +10:00
WithoutPants
3d52bad9bd Add missing gallery card classes (#2654) 2022-06-08 08:58:25 +10:00
stash-translation-bot
803d865348 Translations update from Stash (#2627)
* Translated using Weblate (French)

Currently translated at 88.8% (725 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (German)

Currently translated at 97.6% (797 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (French)

Currently translated at 89.4% (730 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 89.4% (730 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Added translation using Weblate (Hungarian)

* Translated using Weblate (German)

Currently translated at 98.1% (803 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.7% (808 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Polish)

Currently translated at 100.0% (818 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Hungarian)

Currently translated at 48.5% (397 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/hu/

* Translated using Weblate (Italian)

Currently translated at 100.0% (818 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.7% (816 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (818 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Finnish)

Currently translated at 93.7% (767 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (French)

Currently translated at 91.5% (749 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 91.5% (749 of 818 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

Co-authored-by: - <adr.web@hotmail.fr>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Phasetime <phasetime@protonmail.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Foltin <foltincsaba@gmail.com>
Co-authored-by: ponei <poneialt@gmail.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Aa <jarruraita@outlook.com>
2022-06-05 17:06:17 -07:00
Emilo2
8a1c349976 Fix scraping more than 40 scenes from stash-box (#2638) 2022-06-03 09:37:24 +10:00
InfiniteTF
d68d022893 Add support for submitting stash-box scene updates by draft (#2577) 2022-06-01 14:53:31 +10:00
InfiniteTF
e51083c26d Update stash-box fingerprint query to fully support distance matching (#2509) 2022-06-01 12:59:06 +10:00
DingDongSoLong4
49f579e08e Fix gallery updating (#2611) 2022-06-01 11:58:44 +10:00
HijackHornet
1c18ec1501 Fixed windows install links (#2635) 2022-05-30 08:43:01 -07:00
kermieisinthehouse
88111e4064 Remove (Preview) From Korean 2022-05-25 19:37:31 -07:00
kermieisinthehouse
ba2979096a Update README.md 2022-05-25 19:35:24 -07:00
stash-translation-bot
34eb624438 Translations update from Stash (#2604)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.7% (806 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Korean)

Currently translated at 77.6% (634 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ko/

* Translated using Weblate (French)

Currently translated at 86.0% (702 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 87.2% (712 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 87.2% (712 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (Finnish)

Currently translated at 93.2% (761 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (French)

Currently translated at 83.7% (683 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (Korean)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ko/

* Translated using Weblate (French)

Currently translated at 87.6% (715 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 87.5% (714 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (French)

Currently translated at 87.5% (714 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

Co-authored-by: LiboSUN <learbo@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: yc <yechan24680@gmail.com>
Co-authored-by: - <adr.web@hotmail.fr>
Co-authored-by: Aa <jarruraita@outlook.com>
2022-05-25 19:33:11 -07:00
kermieisinthehouse
a142ec223e Add Korean Language (#2601) 2022-05-19 15:38:42 -07:00
WithoutPants
b2ac022357 Update changelog for #2594 2022-05-18 12:11:56 +10:00
stash-translation-bot
2653342ae1 Translations update from Stash (#2589)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Added translation using Weblate (Romanian)

* Translated using Weblate (Romanian)

Currently translated at 5.7% (47 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ro/

* Added translation using Weblate (Korean)

* Translated using Weblate (Korean)

Currently translated at 68.8% (562 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ko/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Imagine <pyrolaziano@protonmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: yc <yechan24680@gmail.com>
2022-05-17 19:07:50 -07:00
DingDongSoLong4
df24f90735 Fix gallery scan (#2594)
The incorrect error check means that images is always 0, which means scanImages is also always true, which causes the images inside the zip to be unnecessarily rescanned every time.
2022-05-18 12:03:54 +10:00
kermieisinthehouse
1200d4472a CSS: Fix mobile filter tags (#2584) 2022-05-16 09:50:51 +10:00
kermieisinthehouse
55366fa66f Add Danish and Polish i18n support (#2586) 2022-05-13 12:45:05 -07:00
stash-translation-bot
c232372f0c Translations update from Stash (#2576)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (797 of 797 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Danish)

Currently translated at 100.0% (815 of 815 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (815 of 815 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Polish)

Currently translated at 99.3% (810 of 815 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Italian)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Polish)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Danish)

Currently translated at 100.0% (816 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

* Added translation using Weblate (Czech)

* Translated using Weblate (Czech)

Currently translated at 0.4% (4 of 816 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/cs/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Hidden Hiddenson <078emil@protonmail.com>
Co-authored-by: Wasylq <Wasylq@protonmail.com>
Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Thor Skinderholm <el.thoro@gmail.com>
Co-authored-by: Mrgrpm <mrgrpm58@gmail.com>
2022-05-13 12:44:41 -07:00
kermieisinthehouse
53c6ae4ea5 Correct 'reload scrapes' path (#2583) (#2585)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
Co-authored-by: techie2000 <38585780+techie2000@users.noreply.github.com>
2022-05-13 12:08:20 -07:00
cj
dce4591911 Recommendations page bug fixes and refactoring (#2578)
* Changed Most Active Studios to Latest Studios
* dynamically create view all link and created message for view all
* created shared determineSlidesToScroll method
* removed added code in Shared/index.ts
* renamed getSlickSettings to getSlickSliderSettings
* Updated row headers to follow plex naming convention
* removed extra s in Sceness
* updated row header css to better align header text with view all anchor
2022-05-12 12:57:41 +10:00
WithoutPants
31cb8e2cbd Fix direct stream not working in Safari (#2581) 2022-05-12 12:10:46 +10:00
WithoutPants
ea2fcd9d7f Improve Handy integration (#2555)
* Refactor interactive into context
* Stop the interactive device when leaving page
* Show interactive state if not ready
* Handle navigation and looping
2022-05-10 16:38:34 +10:00
cj
bc85614ff9 Recommendation home page (#2571) 2022-05-10 11:51:20 +10:00
stash-translation-bot
1e7b85fbe5 Translations update from Stash (#2499)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.8% (790 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Italian)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Polish)

Currently translated at 99.8% (790 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Polish)

Currently translated at 99.7% (792 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Polish)

Currently translated at 99.8% (793 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Danish)

Currently translated at 27.2% (216 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Hidden Hiddenson <078emil@protonmail.com>
2022-05-09 16:48:57 -07:00
WithoutPants
9ca3874707 Fix slideshow autoplaying when on another tab (#2563) 2022-05-06 12:23:06 +10:00
WithoutPants
73ded0d97d Prevent lightbox transition until specific number of scroll events (#2544)
* Delay before nav to next image on scroll
* Add config for scroll attempts before transition
2022-05-06 12:22:26 +10:00
cj
c1a096a1a6 Caption support (#2462)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-05-06 11:59:28 +10:00
HijackHornet
ab1b30ffb7 Clarifying Mingw installation and windows usage (#2566)
Because i lost over an hour making it work
2022-05-05 11:11:52 -07:00
WithoutPants
00e98a9c09 Fix thumbnail generation (#2561) 2022-05-05 12:11:10 +10:00
WithoutPants
d77f47824c Don't show dialog box when run from terminal (#2560) 2022-05-05 11:30:10 +10:00
WithoutPants
727644fab3 Update changelog 2022-05-05 11:09:59 +10:00
WithoutPants
dcd7595c07 Change playback rates and fix rates after seeking (#2550) 2022-05-05 11:06:47 +10:00
WithoutPants
ce175dcfc6 Fix audio not in video previews (#2547) 2022-05-05 11:04:01 +10:00
WithoutPants
1964481ff2 Play scene when scrubber clicked (#2546) 2022-05-05 11:03:46 +10:00
WithoutPants
a8456dd188 Remove windowsgui from cross-compile-windows build target 2022-05-04 17:15:28 +10:00
WithoutPants
a6f15273d9 Improve Windows stash behaviour (#2543)
* Rename manager.instance to Manager
* Show dialog message on fatal error on Windows
* Hide console windows explicitly on icon launch

Gets rid of the windowsgui flag, which causes all sorts of issues.
Instead, checks if stash was launched from the icon, and if so hides the console.

* Remove fixconsole
* Add changelog entries
2022-05-04 10:37:32 +10:00
WithoutPants
36aa51a187 Migrate vtt contents when hash changes (#2554) 2022-05-04 09:29:20 +10:00
WithoutPants
1a6f5619ae Encode query string (#2552) 2022-05-04 09:29:05 +10:00
WithoutPants
21a26fadd8 Generate single preview video for short scenes (#2553) 2022-05-04 09:28:48 +10:00
WithoutPants
20ffd4d51d Accept gif and webp for images (#2551) 2022-05-04 09:27:50 +10:00
WithoutPants
e87fd516d6 Fix streaming scenes not able to be deleted (#2549)
* Don't navigate away from scene if delete failed
* Close connection on cancel
2022-05-04 09:27:22 +10:00
InfiniteTF
0c2dc17e8e Fix crash when cancelling pending tasks (#2527) 2022-04-25 16:21:21 +10:00
WithoutPants
1b91937004 Fix image thumbnail generation (#2524)
* Better logging for thumbnail generation errors
* Reduce verbosity for thumbnail generation
* Provide stdin during thumbnail generation
2022-04-25 15:56:06 +10:00
WithoutPants
9e606feb76 Refresh marker panel on marker create (#2502)
* Update scene markers on create
* Improve display of markers without titles
* Fix marker title not populating
2022-04-21 11:33:04 +10:00
DingDongSoLong4
340f47cda8 Fix GraphQL Playground CSP directives for Firefox (#2518) 2022-04-18 12:25:46 +10:00
WithoutPants
aacf07feef Restructure ffmpeg (#2392)
* Refactor transcode generation
* Move phash generation into separate package
* Refactor image thumbnail generation
* Move JSONTime to separate package
* Ffmpeg refactoring
* Refactor live transcoding
* Refactor scene marker preview generation
* Refactor preview generation
* Refactor screenshot generation
* Refactor sprite generation
* Change ffmpeg.IsStreamable to return error
* Move frame rate calculation into ffmpeg
* Refactor file locking
* Refactor title set during scan
* Add missing lockmanager instance
* Return error instead of logging in MatchContainer
2022-04-18 10:50:10 +10:00
InfiniteTF
cdaa191155 Fix submission of scene drafts without performers (#2515) 2022-04-18 10:43:27 +10:00
SmallCoccinelle
401660e6a3 Hoist context, enable errchkjson (#2488)
* Make the script scraper context-aware

Connect the context to the command execution. This means command
execution can be aborted if the context is canceled. The context is
usually bound to user-interaction, i.e., a scraper operation issued
by the user. Hence, it seems correct to abort a command if the user
aborts.

* Enable errchkjson

Some json marshal calls are *safe* in that they can never fail. This is
conditional on the types of the the data being encoded. errchkjson finds
those calls which are unsafe, and also not checked for errors.

Add logging warnings to the place where unsafe encodings might happen.
This can help uncover usage bugs early in stash if they are tripped,
making debugging easier.

While here, keep the checker enabled in the linter to capture future
uses of json marshalling.

* Pass the context for zip file scanning.

* Pass the context in scanning

* Pass context, replace context.TODO()

Where applicable, pass the context down toward the lower functions in
the call stack. Replace uses of context.TODO() with the passed context.

This makes the code more context-aware, and you can rely on aborting
contexts to clean up subsystems to a far greater extent now.

I've left the cases where there is a context in a struct. My gut feeling
is that they have solutions that are nice, but they require more deep
thinking to unveil how to handle it.

* Remove context from task-structs

As a rule, contexts are better passed explicitly to functions than they
are passed implicitly via structs. In the case of tasks, we already
have a valid context in scope when creating the struct, so remove ctx
from the struct and use the scoped context instead.

With this change it is clear that the scanning functions are under a
context, and the task-starting caller has jurisdiction over the context
and its lifetime. A reader of the code don't have to figure out where
the context are coming from anymore.

While here, connect context.TODO() to the newly scoped context in most
of the scan code.

* Remove context from autotag struct too

* Make more context-passing explicit

In all of these cases, there is an applicable context which is close
in the call-tree. Hook up to this context.

* Simplify context passing in manager

The managers context handling generally wants to use an outer context
if applicable. However, the code doesn't pass it explicitly, but stores
it in a struct. Pull out the context from the struct and use it to
explicitly pass it.

At a later point in time, we probably want to handle this by handing
over the job to a different (program-lifetime) context for background
jobs, but this will do for a start.
2022-04-15 11:34:53 +10:00
WithoutPants
a7beeb32b0 Fix video layout on touch enabled devices (#2501) 2022-04-15 10:51:49 +10:00
dependabot[bot]
50e83a3555 Bump moment from 2.29.1 to 2.29.2 in /ui/v2.5 (#2495)
Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.2.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.1...2.29.2)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 08:51:47 +10:00
WithoutPants
2b8a40e210 Fix scene studio message id 2022-04-11 08:34:09 +10:00
stash-translation-bot
5636b484b1 Translations update from Stash (#2435)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.8% (790 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Italian)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Polish)

Currently translated at 99.8% (790 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (791 of 791 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (794 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Polish)

Currently translated at 99.7% (792 of 794 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
2022-04-10 15:19:36 -07:00
WithoutPants
8cdf0095cc Handle graphql errors correctly during draft submission (#2485) 2022-04-07 09:32:22 +10:00
WithoutPants
346384b6a7 Merge performer list when saving scene from tagger (#2483) 2022-04-07 09:31:37 +10:00
kermieisinthehouse
4c4d029c23 Freebsd compile errors (#2477)
* Fix local docker builds under new projext org

* Fix Desktop package freebsd compile

* Fix build declaration

* rename systray file
2022-04-05 22:35:32 -07:00
WithoutPants
638b2b4a32 Revert "Do not display tabs with no content (#2468)" (#2487)
This reverts commit 0ee8930bdd.
2022-04-06 15:08:54 +10:00
WithoutPants
2ca457c5b2 Revert #2466 due to portrait video issues (#2486) 2022-04-06 14:56:17 +10:00
WithoutPants
b0f6f4b7c3 Use feature request label for feature requests [skip ci] 2022-04-06 14:12:10 +10:00
WithoutPants
a702d0b7e4 Use bug report label for bug reports [skip ci] 2022-04-06 14:10:59 +10:00
WithoutPants
ed08dd4332 Fix CSP directives for playground (#2484) 2022-04-06 11:52:49 +10:00
kermieisinthehouse
312d43d0ff Fix only one tag markers on scene (#2482)
* Fix scene markers, add path to hidden properties

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-04-06 09:43:20 +10:00
WithoutPants
230d8f6028 Make desktop.Start run on main thread (#2475) 2022-04-05 14:58:08 +10:00
WithoutPants
61d9f57ce9 Add ignore autotag flag (#2439)
* Add autoTagIgnored to database schema
* Graphql changes
* UI changes
* Add field to edit performers dialog
* Apply flag to autotag behaviour
2022-04-04 20:03:39 +10:00
cj
2aee6cc18e Update v0140.md file with scene detail page update (#2472) [skip ci] 2022-04-03 23:13:33 -07:00
WithoutPants
eb35bbabb8 Remove redundant message 2022-04-04 10:33:58 +10:00
WithoutPants
e76e4c978f Fix scene edit panel not updating on scene change (#2471) 2022-04-04 10:31:51 +10:00
WithoutPants
92320b3418 Fix lightbox issues (#2457)
* Don't convert localforage to json
* Show correct delay value
* Fix lightbox delay
* Fix delay input losing focus on change
* Fix delay input inconsistencies
* Fix clicking link preventing scroll bars appearing
* Fix slideshow stopping when paging
2022-04-04 06:27:18 +10:00
chetbrinko
6ebf3ddc9e Add support for the Handy APIv2 (#2193)
* Add support for the Handy APIv2

Docs: https://staging.handyfeeling.com/api/handy/v2/docs/

Update axios to 0.24.0 due to a security update

* Upgrade to thehandy 1.0.3
* Use local copy of uploadCsv

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-04-03 15:46:12 +10:00
cj
6ca862be54 Mobile scene details page revamp (#2466)
* mobile scene details page revamp
* removed dup scene-tabs definition
2022-04-03 15:42:39 +10:00
WithoutPants
0ee8930bdd Do not display tabs with no content (#2468) 2022-04-03 07:44:39 +10:00
WithoutPants
f9cf77e3ed Improve bulk performer editing (#2467)
* Cleanup Edit Performers dialog
* Add bulk text inputs to edit performers dialog
* Make bulk update code more generic
* Add remaining performer fields
2022-04-03 07:05:57 +10:00
WithoutPants
9e2261a813 Temporarily disable caching in lint action 2022-04-02 18:41:43 +11:00
SmallCoccinelle
45f700d6ea Support Go 1.18: Upgrade gqlgen to v0.17.2 (#2443)
* Upgrade gqlgen to v0.17.2

This enables builds on Go 1.18. github.com/vektah/gqlparser is upgraded
to the newest version too.

Getting this to work is a bit of a hazzle. I had to first remove
vendoring from the repository, perform the upgrade and then re-introduce
the vendor directory. I think gqlgens analysis went wrong for some
reason on the upgrade. It would seem a clean-room installation fixed it.

* Bump project to 1.18

* Update all packages, address gqlgenc breaking changes

* Let `go mod tidy` handle the go.mod file

* Upgrade linter to 1.45.2

* Introduce v1.45.2 of the linter

The linter now correctly warns on `strings.Title` because it isn't
unicode-aware. Fix this by using the suggested fix from x/text/cases
to produce unicode-aware strings.

The mapping isn't entirely 1-1 as this new approach has a larger iface:
it spans all of unicode rather than just ASCII. It coincides for ASCII
however, so things should be largely the same.

* Ready ourselves for errchkjson and contextcheck.

* Revert dockerfile golang version changes for now

Co-authored-by: Kermie <kermie@isinthe.house>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-04-02 18:08:14 +11:00
WithoutPants
e5c4241180 Don't overwrite with empty values in tagger (#2461) 2022-04-02 17:47:27 +11:00
WithoutPants
b504a89247 Fix video filters (#2459) 2022-04-02 07:12:24 +11:00
WithoutPants
f4a9ea76a1 Fix 10bit vp9 streaming (#2458) 2022-04-02 07:12:07 +11:00
stg-annon
0880991a2c Ignore key combination on KeyboardEvents (#2460) 2022-04-01 12:23:06 +11:00
WithoutPants
e54268cc7a Videojs big buttons (#2455)
* Add big button panel on touch devices
2022-04-01 09:31:32 +11:00
WithoutPants
d262d18f08 Add source selector plugin (#2449) 2022-04-01 08:20:14 +11:00
WithoutPants
6c3b493323 Add persist volume plugin (#2448) 2022-03-31 08:33:16 +11:00
WithoutPants
510bec655b Add marker videojs plugin (#2447) 2022-03-31 08:14:39 +11:00
stg-annon
df982f7528 use min value for skipping forward (#2454)
fixes #2453
2022-03-31 08:14:23 +11:00
cj
41d6ebc11a allow full screen and inline play on iOS (#2450) 2022-03-30 16:21:44 +11:00
kermieisinthehouse
f1b244f0a1 Delete FUNDING.yml 2022-03-29 16:46:19 -07:00
kermieisinthehouse
22e0c9a61a Create FUNDING.yml 2022-03-29 16:37:59 -07:00
WithoutPants
c4c62d2516 Scene player fixes (#2441)
* Fix scene player stopping on save
* Restore previous player hotkeys
* Volume control over seek bar
2022-03-29 17:17:02 +11:00
dependabot[bot]
743bbc4613 Bump minimist from 1.2.5 to 1.2.6 in /ui/v2.5 (#2437)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 21:22:32 -07:00
InfiniteTF
f3355f3da8 Replace JW Player with video.js (#2100)
* Replace JW Player with video.js
* Move HLS stream to bottom of list

HLS doesn't work very well on non-ios devices.

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-03-29 07:17:19 +11:00
WithoutPants
02ee791796 Fix missing date filter (#2434) 2022-03-29 06:45:46 +11:00
WithoutPants
a2c611f90d Fix lightbox issues (#2426)
* Don't handle non-left-click events
* Improve lightbox initial positioning
* Fix crash when navigating left from first image
2022-03-28 09:33:13 +11:00
stash-translation-bot
2099d10734 Translations update from Stash (#2360)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (776 of 776 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Finnish)

Currently translated at 93.9% (729 of 776 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (776 of 776 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (776 of 776 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Italian)

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.5% (769 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Polish)

Currently translated at 98.0% (773 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (788 of 788 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
2022-03-26 11:09:20 -07:00
WithoutPants
37632f985b Fix windows ffmpeg concat path (#2425) 2022-03-25 13:50:02 +11:00
WithoutPants
f581687198 Improve performance of gallery and image queries (#2422)
* Revert to use FindByGalleryID in gallery resolver
* Sort by path in FindByGalleryID
* Optimise queries
2022-03-25 12:00:51 +11:00
WithoutPants
2d227edaf9 Only cache log items that meet minimum log level (#2421) 2022-03-25 12:00:17 +11:00
WithoutPants
994e39eea5 Initialise queryConfig if not present (#2419) 2022-03-24 12:44:33 +11:00
WithoutPants
23c46423be Always open browser from systray (#2418) 2022-03-24 12:13:22 +11:00
bnkai
ba41133745 Fix ffmpeg download (#2416) 2022-03-24 11:06:17 +11:00
bnkai
627c1e9c60 Fix plugin go examples (#2396) 2022-03-24 11:03:32 +11:00
WithoutPants
0cd9a0a474 Python path setting (#2409)
* Add python package
* Add python path backend config
* Add python path to system settings page
* Apply python path to script scrapers and plugins
2022-03-24 09:22:41 +11:00
kermieisinthehouse
329b611348 Fix local docker builds under new projext org (#2411) 2022-03-22 19:35:42 -07:00
WithoutPants
2afb467bb1 Persist lightbox settings (#2406)
* Persist lightbox settings in local forage
* Add lightbox settings to backend
* Add lightbox settings to interface settings page
2022-03-23 08:18:12 +11:00
WithoutPants
4c4cdae1ed Don't include stash_ids if not setting (#2408) 2022-03-23 08:15:30 +11:00
WithoutPants
b4ecb63e1c Use first sorted by path image as cover by default (#2407) 2022-03-23 08:12:02 +11:00
WithoutPants
228e8c9bfd Scroll to next image using lightbox (#2403)
* Scroll at end of image goes to next/previous
* Align bottom image when moving left
2022-03-22 11:00:32 +11:00
WithoutPants
5eee305a33 Fix for #2401 2022-03-21 10:31:32 +11:00
WithoutPants
dd0fa48345 Move tag exclusion to scrape query resolver (#2391) 2022-03-20 19:46:12 +11:00
WithoutPants
e4ad42caf0 Order gallery images by path (#2390) 2022-03-20 17:51:02 +11:00
WithoutPants
6ceb9c73dd Don't generate thumbnails for webp (#2388)
* Don't generate thumbnails for animated webp
* Debug log when writing thumbnail to disk
2022-03-20 17:48:52 +11:00
WithoutPants
f69bd8a94f Restructure go project (#2356)
* Move main to cmd
* Move api to internal
* Move logger and manager to internal
* Move shell hiding code to separate package
* Decouple job from desktop and utils
* Decouple session from config
* Move static into internal
* Decouple config from dlna
* Move desktop to internal
* Move dlna to internal
* Decouple remaining packages from config
* Move config into internal
* Move jsonschema and paths to models
* Make ffmpeg functions private
* Move file utility methods into fsutil package
* Move symwalk into fsutil
* Move single-use util functions into client package
* Move slice functions to separate packages
* Add env var to suppress windowsgui arg
* Move hash functions into separate package
* Move identify to internal
* Move autotag to internal
* Touch UI when generating backend
2022-03-17 11:33:59 +11:00
WithoutPants
dcee874f59 Merge pull request #2387 from WithoutPants/v0.13.1-to-develop
V0.13.1 to develop
2022-03-16 17:21:09 +11:00
WithoutPants
c4ee40a4cb Remove redundant changelog entry 2022-03-16 17:01:14 +11:00
WithoutPants
984eba83bf Merge branch 'develop' into v0.13.1-to-develop 2022-03-16 16:57:52 +11:00
WithoutPants
2b5c5f200d Merge pull request #2386 from WithoutPants/hotfix-0.13.1
Hotfix 0.13.1
2022-03-16 09:29:11 +11:00
WithoutPants
bd917fdc64 Add hotfix version entry 2022-03-16 09:21:30 +11:00
WithoutPants
4d800a14f4 Use case-insensitive matching in nameMatchesPath (#2378)
Also ensures that only unique words are returned in getPathWords.
2022-03-16 09:10:12 +11:00
WithoutPants
9e3d56b22f Fix identify and script scraper bugs (#2375)
* Continue identify if source fails
* Handle empty result set correctly
* Parse null values from scraper script correctly
* Omit warning when json selector value missing
* Return nil when scraped item not found
* Fix graphql validation errors
2022-03-15 09:42:22 +11:00
WithoutPants
e4d6d3b085 Use case-insensitive matching in nameMatchesPath (#2378)
* Use case-insensitive matching in nameMatchesPath

Also ensures that only unique words are returned in getPathWords.
2022-03-11 09:35:48 +11:00
cj
7bd47c651c Read theme color from config file (#2365)
* read theme color from config file
* Update manual page
2022-03-10 08:24:13 +11:00
Still Hsu
a05952829c i18n: Migrate hard strings (#2353)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-03-09 12:42:08 +11:00
WithoutPants
d88515abcd Autotag optimisation (#2368)
* Add duration to autotag finish message
* No sorting scene/image/gallery where not specified
* Use an LRU cache for sqlite regexp function
* Compile path separator regex once
* Cache objects with single letter first names
* Move finished auto-tag log
* Add more verbose logging
* Add new changelog
2022-03-09 12:01:56 +11:00
WithoutPants
740426515e Merge pull request #2373 from stashapp/develop
Fix release asset uploads (#2372)
2022-03-08 11:29:58 +11:00
WithoutPants
9ef3060452 Fix release asset uploads (#2372) 2022-03-08 11:10:56 +11:00
WithoutPants
86cf4d6524 Merge pull request #2369 from stashapp/develop
Merge develop to master for 0.13
2022-03-08 07:04:34 +11:00
WithoutPants
d8cedfaf2f Add missing bug fix changelogs [skip ci] 2022-03-08 07:01:57 +11:00
WithoutPants
18665863d6 Remove single unicode character from autotag query (#2363)
* Remove single unicode character from autotag query
* Compile regex once where possible
* Fix CPU profiling
* Only match unicode characters if in path
2022-03-07 13:26:24 +11:00
WithoutPants
0737ca953d Add title, rating, o-counter to image lightbox (#2274) 2022-03-03 11:39:03 +11:00
WithoutPants
d7473f4b38 Distance match phashes on bulk stash-box query (#2355) 2022-03-03 09:38:37 +11:00
dependabot[bot]
40e092ecc5 Bump url-parse from 1.5.7 to 1.5.10 in /ui/v2.5 (#2352)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-02 09:21:47 +11:00
WithoutPants
89fff76937 Update changelog 2022-02-28 13:16:10 +11:00
WithoutPants
78b7a035d8 Set performer MD5 if name changed in stash-box tag (#2345) 2022-02-28 13:13:13 +11:00
WithoutPants
34a7e67b29 Remove generate preview options from tasks page (#2342) 2022-02-28 13:13:00 +11:00
WithoutPants
1ab5be162e Handle unicode characters in autotag (#2336) 2022-02-28 13:12:43 +11:00
stash-translation-bot
c5c94e783e Translations update from Stash (#2267)
* Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (730 of 730 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (German)

Currently translated at 100.0% (730 of 730 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (731 of 731 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (731 of 731 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Italian)

Currently translated at 100.0% (731 of 731 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (German)

Currently translated at 100.0% (731 of 731 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Finnish)

Currently translated at 95.2% (696 of 731 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Added translation using Weblate (Polish)

* Translated using Weblate (Polish)

Currently translated at 5.3% (41 of 773 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (773 of 773 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Polish)

Currently translated at 17.4% (135 of 773 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Polish)

Currently translated at 18.6% (144 of 773 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Polish)

Currently translated at 27.9% (216 of 773 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (774 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Polish)

Currently translated at 38.8% (301 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pl/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (774 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (774 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Italian)

Currently translated at 100.0% (774 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (774 of 774 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

Co-authored-by: phasetime <4@example.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Victor Almeida <vhca8797@gmail.com>
2022-02-26 10:30:58 -08:00
WithoutPants
10bd202df3 Disable macos bundling until issues resolved (#2343)
* Disable macos bundling until issues resolved
* Update README
2022-02-25 11:04:25 +11:00
WithoutPants
7fd8fc8d55 Remove ChunkDuration warning (#2346)
ChunkDuration is only used in preview generation, and is already validated there.
2022-02-25 11:03:41 +11:00
kermieisinthehouse
4e9a635ed8 Add filesize on scene cards, invisibly (#2335)
* Add filesize on scene cards, in secret
* Add find extra copies button, also hidden
2022-02-24 09:27:46 +11:00
dependabot[bot]
299d59b51e Bump url-parse from 1.5.3 to 1.5.7 in /ui/v2.5 (#2331)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.7.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.7)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-22 11:35:40 -08:00
Releck
22321c2b62 Fix performer tags not applying on scene scrapers (#2339) 2022-02-22 10:18:29 +11:00
WithoutPants
77e95cdb07 Add recent changelog entries 2022-02-20 11:11:25 +11:00
WithoutPants
3475a33aab Combine system preview generation options (#2328) 2022-02-20 11:08:21 +11:00
WithoutPants
d678283486 Add setting for showing scene scrubber by default (#2325) 2022-02-20 11:08:05 +11:00
WithoutPants
4a6e26dcc8 Add gallery icon to image card (#2324) 2022-02-20 11:07:47 +11:00
WithoutPants
1fab368328 Add missing card classes (#2327)
* Move performer flag and add classes
* Add class to interactive heat map
* Add classes to scene card
* Add missing class names
2022-02-20 11:05:51 +11:00
DampToast
77acbc42b7 Fix tattoos being put in twitter on batch performer (#2332) 2022-02-19 09:06:20 +11:00
WithoutPants
56e12303bc Internationalise hard-coded strings (#2320) 2022-02-19 09:05:30 +11:00
kermieisinthehouse
b49695016f Fix Performer Save Edit button position (#2329) 2022-02-19 09:04:59 +11:00
kermieisinthehouse
36ce75c8c6 Fix windows systray icon just hanging out (#2330) 2022-02-18 15:03:41 +11:00
WithoutPants
358545579a Attempted spinlock fix (#2321) 2022-02-18 10:40:03 +11:00
DampToast
c62b0b28ed Performer tagger modal will load country name instead of code (#2323)
* Performer tagger modal will load country name instead of code

When using the performer tagger, it would pull the 2 character country code from StashDB and store that.  It now shows the full country name and stores the full country name instead.  Also verified that it causes no issue with a performer that has no country set on StashDB
2022-02-17 14:25:34 +11:00
WithoutPants
429fc3869b Clamp generator parameters (#2319) 2022-02-17 09:33:37 +11:00
DampToast
9321388770 Scan now uses exclude regex on gallery zips (#2317)
* Fix zip galleries not using regex excludes
2022-02-16 13:46:35 +11:00
mmavx
3fdc32b432 Add bulk movie update (#2283)
* Add bulk movie edit dialog
* Implement common bulk edit functions

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-02-16 13:03:57 +11:00
Still Hsu
da600d19d5 Localize hard strings in submission & performer tagger (#2304)
* Localize hard strings in submission & performer tagger
* Localise show/hide configuration

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-02-16 11:41:08 +11:00
cj
7336a6cd18 Update apple touch icon (#2295) 2022-02-16 11:35:45 +11:00
dependabot[bot]
c28110ffb0 Bump follow-redirects from 1.14.7 to 1.14.8 in /ui/v2.5 (#2315)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-16 11:34:33 +11:00
DampToast
de2724abb9 True case insensitive regex support (#2314) 2022-02-16 11:34:08 +11:00
kermieisinthehouse
4dd0bbc294 Add Several Media Performer Detail Filters, Scene Filters and Sort (#2257)
Co-authored-by: bnkai <48220860+bnkai@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-02-16 11:11:57 +11:00
DampToast
1d68492a5b Have Gallery info panel say "Downloaded From" next to URL (#2312)
* Make changes to fix special characters in Criterion labels (#1819)

Reverse the '&' and '+' replacement done on StringCriterion
Decodes special characters in IHierarchicalLabeledIdCriterion

* Have Gallery info panel say "Downloaded From" next to URL

In the gallery info panel it says "Path" next to the URL.  This updates it to say "Downloaded From" next to the URL, like it does in the scene view for the URL.
2022-02-13 17:08:58 -08:00
mmavx
516f223329 Add additional fields to bulk performer dialog (#2282)
* Add country to EditPerformersDialog

* Add most text fields to EditPerformersDialog

* Refactor to pass validate

* Remove height and measurements fields

* Add gender field

* Run fmt-ui
2022-02-09 18:12:12 -08:00
kermieisinthehouse
ddf38726a4 Cleanup logspam in identify (#2287) 2022-02-09 18:11:23 -08:00
DampToast
67b1d15efc Make changes to fix special characters in Criterion labels (#1819) (#2301)
Reverse the '&' and '+' replacement done on StringCriterion
Decodes special characters in IHierarchicalLabeledIdCriterion
2022-02-09 17:49:25 -08:00
kermieisinthehouse
47670d1e94 Fix typo, whoops (#2298) 2022-02-08 01:02:22 -08:00
mmavx
1523985cc2 Disable image lightbox for multi-selection (#2294)
* Disable image lightbox for multi-selection

* Update ImageList.tsx

Co-authored-by: kermieisinthehouse <kermie@isinthe.house>
2022-02-08 00:32:16 -08:00
Jeremy Meyers
cee36cb17c Cleaned up some language. (#2209)
* Cleaned up some language.

- Optimized the Quick Start guide, moved "how do i get to stash" from FAQ, moved some sections together.

* as above

* Update README.md

Co-authored-by: bnkai <48220860+bnkai@users.noreply.github.com>

* Some editing and notes

* Remove double click line, let's not patronize :)

Co-authored-by: kermieisinthehouse <kermie@isinthe.house>
Co-authored-by: bnkai <48220860+bnkai@users.noreply.github.com>
2022-02-06 20:37:39 -08:00
cj
c857e30120 Add proper home screen icon for mobile users (#2279)
* Add proper home screen icon for mobile users

* Updated hrefs to use relative path to favicon.ico
2022-02-03 16:27:33 -08:00
kermieisinthehouse
ce9552e5a8 Fix MacOS Links in README (#2280) [skip ci] 2022-02-02 23:19:29 -08:00
kermieisinthehouse
21a86bb452 Fix build for renamed armv6 (#2278) 2022-02-03 11:59:00 +11:00
kermieisinthehouse
10bb9a6abc Delete funscripts while deleting scene (#2265)
* Delete funscripts while deleting scene
* Indicate that funscripts will be deleted

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-02-03 11:58:48 +11:00
kermieisinthehouse
0e514183a7 Desktop integration (#2073)
* Open stash in system tray on Windows/MacOS
* Add desktop notifications
* MacOS Bundling
* Add binary icon

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-02-03 11:20:34 +11:00
WithoutPants
e48b2ba3e8 Add lightbox preview button to image card (#2275)
* Add lightbox preview button to image card
* Always show preview button on touch screen
2022-02-03 10:41:56 +11:00
kermieisinthehouse
def9ad88b0 Remove trusted proxies (#2229) 2022-02-03 10:16:22 +11:00
InfiniteTF
a3c20ce8da Add support for submitting performer/scene drafts to stash-box (#2234)
* Add support for submitting performer/scene drafts to stash-box

Co-authored-by: Kermie <kermie@isinthe.house>
2022-02-01 15:06:51 +11:00
kermieisinthehouse
c5cd0e1c9c Fix button spacing, fix gender/country height (#2220)
* Fix button spacing, fix gender/country height, add tombstones

* Remove Tombstones
2022-01-29 13:02:54 -08:00
kermieisinthehouse
184117ea39 Add Tasks table to Logs Page (#2246)
* Add Tasks table to logs page

* Fix line height
2022-01-28 23:46:52 -08:00
dependabot[bot]
59668d7aae Bump nanoid from 3.1.22 to 3.2.0 in /ui/v2.5 (#2252)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.22 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.22...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-28 08:51:18 +00:00
kermieisinthehouse
ed58230f3f Add Japanese, upgrade Dutch and Portuguese (#2259)
* Add Japanese, upgrade Dutch and Portuguese

* Add to readme [skip ci]
2022-01-28 08:20:34 +00:00
stash-translation-bot
06ad15541d Translations update from Stash (#2232)
* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 97.8% (713 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Finnish)

Currently translated at 95.4% (696 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Russian)

Currently translated at 44.3% (323 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Added translation using Weblate (Japanese)

* Translated using Weblate (Japanese)

Currently translated at 14.8% (108 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Spanish)

Currently translated at 95.7% (698 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/pt_BR/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ja/

* Translated using Weblate (Danish)

Currently translated at 16.5% (121 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Victor Almeida <vhca8797@gmail.com>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: Konstantin <vinyl.maniac.psoft@gmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Hidden Hiddenson <078emil@protonmail.com>
2022-01-28 08:06:11 +00:00
dumdum7
cfd0f1d74f Use <video> "muted" attribute in video preview (#2254) 2022-01-27 22:15:32 -08:00
Viacheslav Poturaev
d9fb51f2f9 Reuse static server for multiple requests (#2258) 2022-01-28 15:20:05 +11:00
WithoutPants
0388aec942 Only update tags if not dirtied (#2241) 2022-01-20 08:10:47 +01:00
dependabot[bot]
d985e5d9b7 Bump follow-redirects from 1.14.4 to 1.14.7 in /ui/v2.5 (#2238)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-17 14:50:38 +11:00
InfiniteTF
4bbf511954 Prevent creation of dupe filters (#2233) 2022-01-16 10:24:25 +11:00
InfiniteTF
51bb602dc2 Fix vite manifest.json crash (#2237) 2022-01-16 10:14:31 +11:00
InfiniteTF
db55b1746a Show country flags for ISO coded countries (#2230) 2022-01-12 11:34:11 +11:00
stash-translation-bot
7a0aa5d94a Translations update from Stash (#2181)
* Translated using Weblate (Russian)

Currently translated at 14.6% (106 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Translated using Weblate (German)

Currently translated at 99.5% (720 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Dutch)

Currently translated at 38.4% (278 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Finnish)

Currently translated at 91.4% (661 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Russian)

Currently translated at 37.8% (274 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Translated using Weblate (Spanish)

Currently translated at 95.5% (691 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (German)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (German)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/tr/

* Added translation using Weblate (Danish)

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Danish)

Currently translated at 3.0% (22 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

* Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Finnish)

Currently translated at 95.4% (696 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Danish)

Currently translated at 16.0% (117 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/da/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (French)

Currently translated at 96.4% (703 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (German)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Russian)

Currently translated at 37.7% (275 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Translated using Weblate (Dutch)

Currently translated at 78.6% (573 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Spanish)

Currently translated at 95.1% (694 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Dutch)

Currently translated at 83.6% (610 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (729 of 729 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

Co-authored-by: pixeldonut <nehoroso45@gmail.com>
Co-authored-by: Niemand <Niemandes@protonmail.com>
Co-authored-by: irazox <robinvdv@live.com>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: phasetime <4@example.com>
Co-authored-by: UncleRoger33 <matarama_s@yahoo.com>
Co-authored-by: Hidden Hiddenson <078emil@protonmail.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Stashing  OpenSource <sqdsqdqsd5d4qs56d456q@yopmail.com>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Christoph Mühlbacher <sck65666@boofx.com>
Co-authored-by: greatcb12 <greatcb12@gmail.com>
Co-authored-by: Kermie <kermie@arcticpornvault.org>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
2022-01-11 10:30:33 +11:00
stg-annon
20999cf65b add link to Scene Filename Parser docs (#2212) 2022-01-11 10:24:51 +11:00
InfiniteTF
b527ac5d1f Fix performer tagger gender setting (#2222) 2022-01-11 10:23:29 +11:00
InfiniteTF
df1478c25d Only show one modal when tagging performers (#2223) 2022-01-11 10:20:50 +11:00
kermieisinthehouse
e1cf695b65 Deprecate forwarded port (#2228) 2022-01-11 10:09:14 +11:00
kermieisinthehouse
28c72d3ee3 Allow stash to be iframed (#2217) 2022-01-11 10:05:12 +11:00
kermieisinthehouse
13b60e7218 Add preview languages (#2194)
* Add preview languages
* Turkish is completed
* Update README.md
2022-01-05 14:21:15 +11:00
kermieisinthehouse
baf148625c Touch up Performer Page (#2200)
* Moves "Edit" and "Autotag" out of performer tabs
* Smoothen out fedit submission behavior
2022-01-05 14:18:57 +11:00
bnkai
be5dc7e545 Resolve hostname for chromium RDP requests (#2174) 2022-01-04 15:47:39 +11:00
kermieisinthehouse
1714efc92f Add Gender Icons to Performers (#2179) 2022-01-04 15:09:03 +11:00
InfiniteTF
34aea876e8 Add stash-box credentials validation (#2173) 2022-01-04 14:20:31 +11:00
kermieisinthehouse
90a4931bdc Show counts on entity list tabs (#2169) 2022-01-04 14:04:50 +11:00
bnkai
849c590b2a Fix scrubber sprite creation for short video files (#2167)
* Fix scrubber sprite creation for small files
* accept only valid ffprobe nbReadFrames
2022-01-04 13:46:53 +11:00
InfiniteTF
bd784cdf96 Fix conversion of multi word stash-box enums (#2191) 2022-01-04 12:55:45 +11:00
InfiniteTF
a2bfa9ee79 Fix stash-box batch performer birthdate update (#2189) 2022-01-04 12:15:38 +11:00
bnkai
0c0bdd4e21 Fix oshash calculation for symlinks (#2198) 2022-01-04 11:30:42 +11:00
peolic
19fbf125c4 Fix CreatedAt/UpdatedAt timezone and missing time [+ v0.13.0 changelog] (#2190)
* Fix CreatedAt/UpdatedAt timezone and missing time
* Create v1.13.0 changelog
2022-01-04 10:06:12 +11:00
peolic
7f831524f6 Explain recursive common fragments edge case (#2183) 2021-12-30 10:49:47 +11:00
UncleRoger33
e038da433f Fix broken and outdated links in the Help document (#2180)
* Update Contributing.md

Fixed readme.md link.

* Update Introduction.md

"/settings?tab=configuration" is no longer valid. It is "/settings?tab=libraries" now.

* Update Interface.md

Fixed broken "Stash Plex Theme" link.

* Update ScraperDevelopment.md

Fixed broken "golang fields" link.

* Update Interactive.md

Added missing title.

* Update Contributing.md

Changed link to the correct document -->
4c71a7dc1b (r776229444)
2021-12-30 10:46:49 +11:00
WithoutPants
19e22e241b Merge master to develop for tag
Merge master to develop for tag
2021-12-30 10:40:33 +11:00
WithoutPants
fb864a6e90 Merge pull request #2175 from stashapp/develop
Merge to master for v0.12.0 release
2021-12-29 09:39:32 +11:00
stash-translation-bot
98484a9d37 Translations update from Stash (#2162)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (German)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Added translation using Weblate (Turkish)

* Translated using Weblate (Turkish)

Currently translated at 27.1% (196 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/tr/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Added translation using Weblate (Russian)

* Translated using Weblate (Russian)

Currently translated at 5.3% (39 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Translated using Weblate (Turkish)

Currently translated at 28.3% (205 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/tr/

* Translated using Weblate (Russian)

Currently translated at 6.3% (46 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/ru/

* Translated using Weblate (Spanish)

Currently translated at 94.8% (686 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: I DONT KNOW <tafir85315@wolfpat.com>
Co-authored-by: Gürkan <paraband@gurkan.in>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Vladislav Iurciuc <vladislav.iurciuc@yandex.ru>
Co-authored-by: Kermie <kermie@arcticpornvault.org>
2021-12-29 09:22:25 +11:00
WithoutPants
7a406ef7f9 Rearrange changelog for release 2021-12-29 09:12:40 +11:00
stash-translation-bot
97b6f5f5c7 Translations update from Stash (#2081)
* Translated using Weblate (Finnish)

Currently translated at 94.6% (649 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (689 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Finnish)

Currently translated at 94.9% (654 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (German)

Currently translated at 100.0% (689 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Dutch)

Currently translated at 25.3% (175 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Dutch)

Currently translated at 30.3% (209 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Dutch)

Currently translated at 30.6% (211 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Italian)

Currently translated at 100.0% (689 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (689 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Dutch)

Currently translated at 39.6% (273 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/nl/

* Translated using Weblate (Spanish)

Currently translated at 99.8% (688 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Croatian)

Currently translated at 7.9% (55 of 689 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/hr/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (French)

Currently translated at 96.7% (695 of 718 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (718 of 718 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (French)

Currently translated at 96.1% (690 of 718 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fr/

* Update translation files

Updated by "Remove blank strings" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (German)

Currently translated at 100.0% (719 of 719 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (German)

Currently translated at 100.0% (719 of 719 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (719 of 719 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (German)

Currently translated at 100.0% (719 of 719 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (723 of 723 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Martin Elgert <martin.elgert@tutanota.com>
Co-authored-by: Two Nipples <stash@k.candelae.re>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Oaquasis <social@brons.pro>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Ivy JP <ivi104.wiki@gmail.com>
Co-authored-by: Stashing  OpenSource <sqdsqdqsd5d4qs56d456q@yopmail.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: I DONT KNOW <tafir85315@wolfpat.com>
Co-authored-by: thoschw <thoschw97@gmail.com>
2021-12-22 11:11:00 +11:00
kermieisinthehouse
deb5a2958d Make phash a link to duplicates (#2154) 2021-12-22 10:51:49 +11:00
WithoutPants
523edfb575 Fix endless toast errors in Settings (#2146) 2021-12-22 10:43:30 +11:00
InfiniteTF
cfcc6aff6a Update styling of folder select error state (#2150)
* Update styling of folder select error state
* Decrease icon margin
2021-12-20 22:44:26 +11:00
meed69
4927bb078c Creation and update dates visible for images and scenes (#2145)
* Exposed created_at and updated_at dates on the detail panels for images and scenes
* Add fields to gallery page
* Internationalisation

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-12-20 13:20:08 +11:00
WithoutPants
bdfb8ad567 Support setting scrapers path in UI (#2124)
* Support setting scrapers path in UI
* Refresh scrapers when scrapers path changes
2021-12-20 12:00:26 +11:00
WithoutPants
b0cf04865a Add force transcode option (#2126) 2021-12-20 11:45:36 +11:00
bnkai
f830d9cf13 Fix UseFileMetadata when scanning (#2138) 2021-12-19 14:03:46 +11:00
kermieisinthehouse
65b8a3fe96 Whitelist CDN used by playground (#2139) 2021-12-18 11:09:39 +11:00
kermieisinthehouse
bf1b835d6d Fix ffmpeg downloader on windows (#2133) 2021-12-16 13:29:42 +11:00
WithoutPants
d25510fdd7 Selective clean (#2125)
* Add backend support for selective clean
* Add selective clean button and dialog
2021-12-16 13:28:44 +11:00
kermieisinthehouse
d94e4f9a5b Add short sprite error logging (#2129) 2021-12-16 11:35:22 +11:00
bnkai
66dd239732 Skip cleaning for search by name scrape queries (#2059)
* Skip pp for search by name queries
* upgrade htmlquery
2021-12-16 11:18:39 +11:00
WithoutPants
439c338049 Add media-src to content-security-policy (#2132) 2021-12-16 09:18:23 +11:00
kermieisinthehouse
bbe99a0dd8 Fix Safari websockets requests (#2128) 2021-12-15 21:07:12 +11:00
WithoutPants
920ad1074f Player improvements (#2099)
* Focus scene player on ready
* Change file info hotkey
* Add hide/show sidebar hotkey
* Add hotkey to hide scrubber
* Update keybinds manual page
2021-12-15 14:22:10 +11:00
InfiniteTF
b58883c074 Fix image querying (#2119)
* Fix image querying
* Add unit tests

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-12-15 13:56:03 +11:00
WithoutPants
d176e9f192 Settings UI refactor (#2086)
* Full width settings page
* Group settings
* Make config fields optional
* auto save on change
* Add settings context
* Refactor stash library section
* Restructure settings
* Refactor tasks page
* Add collapse buttons for setting groups
* Add collapse buttons in library
* Add loading indicator
* Simplify task options. Add details to manual
* Add manual links to tasks page
* Add help tooltips
* Refactor about page
* Refactor log page
* Refactor tools panel
* Refactor plugin page
* Refactor task queue
* Improve disabled styling
2021-12-14 15:06:05 +11:00
kermieisinthehouse
b4b955efc8 Fix images loading from stashdb (#2115)
* Allow images from any source

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-12-14 10:13:23 +11:00
kermieisinthehouse
98e836fdb3 Fix auth in CSP (#2112) 2021-12-13 15:45:02 +11:00
kermieisinthehouse
b86c9fa8fe Security Hardening: Content Security Policy + more (#2108)
* Add CSP, varied security headers
2021-12-13 14:54:19 +11:00
WithoutPants
79e01589ca Include path and hashes in destroy hook input (#2102) 2021-12-13 14:38:00 +11:00
kermieisinthehouse
9a8f05d826 Marker previews should respect preview audio setting (#2101) 2021-12-13 14:09:00 +11:00
WithoutPants
310f181317 Stick edit scene buttons to top (#2098) 2021-12-13 13:44:46 +11:00
agentfisk
e8447c520a Heatmap and speed sorting/filtering for interactive scenes [rewrite] (#2096)
* add InteractiveSpeed to scene model
* add InteractiveHeatmapSpeedGenerator
* add GenerateInteractiveHeatmapSpeedTask
* add InteractiveHeatmapSpeedTask to GenerateJob
* add InteractiveHeatmap on sceneRoutes
* delete heatmap when scene is destroyed
* render interactive heatmap in GridCard
* render InteractiveSpeed on SceneCard
* render InteractiveSpeed in SceneFileInfoPanel
* InteractiveSpeed filters
2021-12-13 13:41:07 +11:00
kermieisinthehouse
d659411c61 Update Tasks documentation (#2079) 2021-12-13 12:20:43 +11:00
bnkai
9bd18c20af Upgrade ansi-regex,normalize-url,postcss,ws (dependabot alerts) (#2090) 2021-12-08 09:36:55 +11:00
InfiniteTF
f3ab6578d9 Add performer aliases to stash-box tagging/scraping (#2091)
* Add performer aliases to stash-box tagging/scraping
2021-12-08 09:36:06 +11:00
Esteban Sanchez
70d9a05580 Use inner join when getting images in a gallery (#2083)
* Added joinType to join struct
* Added addInnerJoin function to perform INNER JOIN type of joins
* Added innerJoin function to perform INNER JOIN type of joins
* Use inner joins when querying images in a gallery
* Renamed addJoin to addLeftJoin
2021-12-06 12:30:40 +11:00
WithoutPants
2460664dc3 Include sceneIDs when generating (#2087) 2021-12-02 09:22:06 +11:00
WithoutPants
7c44a9c993 Refactor tasks follow up (#2061)
* Move scan options out of dialog
* Move autotag and clean options out of dialogs
* Move generate options out of dialog
* Animate button while tasks running
* Revert to earlier Tasks UI iteration
* Rearrange and clarify scan options
2021-11-30 09:45:36 +11:00
SmallCoccinelle
cf4ab843f6 Fix setting images (#2068)
When postprocessing, pass the images by reference rather than value,
so we get the Image fields populated correctly in the output.
2021-11-29 14:54:01 +11:00
kermieisinthehouse
7953fa356f Add Finnish Locale (#2075)
* Add Finnish Locale
* Update readme
2021-11-29 14:43:18 +11:00
WithoutPants
9ebf8331ac Refactor file deletion (#1954)
* Add file deleter
* Change scene delete code
* Add image/gallery delete code
* Don't remove stash library paths
* Fail silently if file does not exist
2021-11-29 14:08:32 +11:00
stash-translation-bot
17aa17fccc Translations update from Stash (#2047)
* Translated using Weblate (German)

Currently translated at 88.6% (604 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (681 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (German)

Currently translated at 91.6% (624 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Added translation using Weblate (Croatian)

* Translated using Weblate (Italian)

Currently translated at 100.0% (681 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Croatian)

Currently translated at 3.9% (27 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/hr/

* Translated using Weblate (German)

Currently translated at 100.0% (681 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (681 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (681 of 681 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Added translation using Weblate (Finnish)

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Finnish)

Currently translated at 13.2% (91 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Finnish)

Currently translated at 30.3% (208 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (German)

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Italian)

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hant/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/sv/

* Translated using Weblate (Finnish)

Currently translated at 43.5% (299 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (686 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Finnish)

Currently translated at 94.6% (649 of 686 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/fi/

* Rename nl.json to nl-NL.json

Co-authored-by: Kermie <kermie@arcticpornvault.org>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Peter M <coceexo@protonmail.com>
Co-authored-by: Balkanac <racunkorac@gmail.com>
Co-authored-by: phasetime <4@example.com>
Co-authored-by: Aspirin <ch3690@outlook.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
2021-11-29 13:51:23 +11:00
Releck
59883df115 Fix identify ScrapedScene conversion (#2071) 2021-11-29 13:42:23 +11:00
SmallCoccinelle
09c4c4173d Support a minModTime input on metadata scans. (#1951)
* Support a maxAge input on metadata scans.

Extend the GraphQL world with a Duration scalar. It is parsed as a
typical Go duration, i.e., "4h" is 4 hours. Alternatively, one can
pass an integer which is interpreted as seconds.

Extend Mutation.metadataScan(input: $input) to support a new optional
value, maxAge. If set, the scanner will exit early if the file it
is looking at has an mtime older than the cutOff point generated by

now() - maxAge

This speeds up scanning in the case where the user knows how old the
changes on disk are, by exiting the scan early if that is the case.

* Change maxAge into minModTime

Introduce a `Timestamp` scalar, so we have a scalar we control. Let
it accept three formats:

* RFC3339Nano
* @UNIX where UNIX is a unix-timestamp: seconds after 01-01-1970
* '<4h': a timestamp relative to the current server time

This scalar parses to a time.Time.

Use MinModTime in the scanner to filter out a large number of scan
analyzes by exiting the scan operation early.

* Heed the linter, perform errcheck

* Rename test vars for consistency.

* Code review: move minModTime into queuefiles

* Remove the ability to input Unix timestamps

Test failures on the CI-system explains why this is undesirable. It is
not clear what timezone one is operating in when entering a unix
timestamp. We could go with UTC, but it is so much easier to require an
RFC3339 timestamp, which avoids this problem entirely.

* Move the minModTime field into filters

Create a new filter input object for metadata scans, and push the
minModTime field in there. If we come up with new filters, they can
be added to that input object rather than cluttering the main input
object.

* Use utils.ParseDateStringAsTime

Replace time.Parse with utils.ParseDateStringAsTime

While here, add some more test cases for that parser.
2021-11-26 11:48:31 +11:00
SmallCoccinelle
4089fcf1e2 Scraper refactor middle (#2043)
* Push scrapeByURL into scrapers

Replace ScrapePerfomerByURL, ScrapeMovie..., ... with ScrapeByURL in
the scraperActionImpl interface. This allows us to delete a lot of
repeated code in the scrapers and replace the central part with a
switch on the scraper type.

* Fold name scraping into one call

Follow up on scraper refactoring. Name scrapers use the same code path.
This allows us to restructure some code and kill some functions, adding
variance to the name scraping code. It allows us to remove some code
repetition as well.

* Do not export loop refs.

* Simplify fragment scraping

Generalize fragment scrapers into ScrapeByFragment. This simplifies
fragment code flows into a simpler pathing which should be easier
to handle in the future.

* Eliminate more context.TODO()

In a number of cases, we have a context now. Use the context rather than
TODO() for those cases in order to make those operations cancellable.

* Pass the context for the stashbox scraper

This removes all context.TODO() in the path of the stashbox scraper,
and replaces it with the context that's present on each of the paths.

* Pass the context into subscrapers

Mostly a mechanical update, where we pass in the context for
subscraping. This removes the final context.TODO() in the scraper
code.

* Warn on unknown fields from scripts

A common mistake for new script writers are that they return fields
not known to stash. For instance the name "description" is used rather
than "details".

Decode disallowing unknown fields. If this fails, use a tee-reader to
fall back to the old behavior, but print a warning for the user in this
case. Thus, we retain the old behavior, but print warnings for scripts
which fails the more strict unknown-fields detection.

* Nil-check before running the postprocessing chain

Fixes panics when scraping returns nil values.

* Lift nil-ness in post-postprocessing

If the struct we are trying to post-process is nil, we shouldn't
enter the postprocessing flow at all. Pass the struct as a value
rather than a pointer, eliminating nil-checks as we go. Use the
top-level postProcess call to make the nil-check and then abort there
if the object we are looking at is nil.

* Allow conversion routines to handle values

If we have a non-pointer type in the interface, we should also convert
those into ScrapedContent. Otherwise we get errors on deprecated
functions.
2021-11-26 11:20:06 +11:00
WithoutPants
19e69f5310 Prefer studio name later in filename (#2057) 2021-11-26 08:29:25 +11:00
WithoutPants
ff32f22c1a Tasks page refactor (#1949)
* Add scan dialog
* Add Auto Tag dialog
* Refactor and combine Generate dialog
* Add clean dialog
* Support scan task default setting
* Support saving auto tag defaults
* Support for generate defaults
2021-11-24 09:09:47 +11:00
WithoutPants
9e558d92f2 Select first tagger result if resolved (#2051) 2021-11-23 08:21:53 +11:00
WithoutPants
c98ad7a62c Validate SQLiteDate (#2052)
* Reject invalid SQLiteDate strings
* Add unit tests
* Handle null values
2021-11-23 08:18:43 +11:00
WithoutPants
f2903d4dcf Fix setting autostart options (#2050) 2021-11-22 16:19:59 +11:00
WithoutPants
2277d0a919 Add search string parsing (#1982)
* Add search string parsing
* Add manual page
2021-11-22 14:59:22 +11:00
kermieisinthehouse
27c0fc8a18 Varied macOS fixes (#2044)
* Fix selects, fix ffprobe download, fix baseurl in dev server
2021-11-22 12:44:24 +11:00
InfiniteTF
7f94165769 Fix hamming distance calculation in tagger (#2040)
* Fix hamming distance calculation in tagger
2021-11-22 11:37:41 +11:00
stash-translation-bot
8a9921b906 Translations update from Stash (#2035)
Co-authored-by: Kermie <kermie@arcticpornvault.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Slorkman <rbuitenga@gmail.com>
Co-authored-by: Peter M <coceexo@protonmail.com>
Co-authored-by: peolic <peolic@protonmail.com>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: Niemand <21havefun@web.de>
Co-authored-by: phasetime <4@example.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
2021-11-21 11:03:13 +11:00
kermieisinthehouse
74fcaa9a3f Reenable gzipping, pull in statigz fix, fix proxy prefix (#2039)
Co-authored-by: peolic <66393006+peolic@users.noreply.github.com>
2021-11-19 13:30:21 +11:00
Por Nulus
9a76b4cf11 Add forward seeking buttons to jwplayer (#1973)
* Add forward seeking buttons to jwplayer

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-19 12:59:02 +11:00
SmallCoccinelle
92a5d96d3b Add a TagMergePost post-hook (#2010)
* Add a TagMergePost post-hook

Introduce a post-hook for merging tags, call it in the merge path, as
the last thing we do before returning.
2021-11-19 11:12:34 +11:00
SmallCoccinelle
c1f89611e2 Refactor scraper top half (#1893)
* Simplify scraper listing

Introduce an enum, scraper.Kind, which explains what we are looking
for. Make it possible to match this from a scraper struct.

Use the enum to rewrite all the listing code to use the same code path.

* Use a map, nitpick ScrapePerformerList

Let the cache store a map from ID of a scraper to the scraper. This
improves lookups when there are many scrapers, making it practically
O(1) rather than O(n). If many scrapers are stored, this is faster.

Since range expressions work unchanged, we don't have to change much,
and things will still work.

make Kind a Stringer

Rename ScraperPerformerList -> ScraperPerformerQuery since that name
is used in the other scrapers, and we value consistency.

Tune ScraperPerformerQuery:

* Return static errors
* Use the new functionality

* When loading scrapers, do so directly

Rather than first walking the directory structure to obtain file paths,
fold the load directly in the the filepath walk. This makes the code
for more direct.

* Use static ErrNotFound

If a scraper isn't found, return one static error. This paves the way
for eventually doing our own error-presenter in gqlgen.

* Store the cache in the Resolver state

Putting the scraperCache directly in the resolver avoids the need to
call manager.GetInstance() all over the place to get access to the
scraper cache. The cache is stored by pointer, so it should be safe,
since the cache will just update its internal state rather than being
overwritten.

We can now utilize the resolver state to grab the cache where needed.

While here, pass context.Context from the resolver down into a function,
which removes a context.TODO()

* Introduce ScrapedContent

Create a union in the GraphQL schema for all scraped content. This
simplifies the internal implementation because we get variance on
the output content type.

Introduce a new type ScrapedContentType which signifies the scraped
content you want as a caller.

Use these to generalize the List interface and the URL scraping
interface.

* Simplify the scraper API

Introduce a new interface for scraping. This interface is then
used in the upper half of the scraper code, to make the code use one
code flow rather than multiple code flows. Variance is currently at
the old scraper structure.

Add extending interfaces for the different ways of invoking scrapes.
Use interface conversions to convert a scraper from the cache to a
scraper supporting the extra methods.

The return path returns models.ScrapedContent.

Write a general postProcess function in the scraper, handling all
ScrapedContent via type switching. This consolidates all postprocessing
code flows.

Introduce marhsallers in the resolver code for converting ScrapedContent
into the underlying concrete types. Use this to plug the existing
fields in the Query resolver, so everything still works.

* ScrapedContent: add more marshalling functions

Handle all marshalling of ScrapedContent through marhsalling functions.
Removes some hand-rolled early variants of it, and replaces it with
a canonical code flow.

* Support loadByName via scraper_s

In order to temporarily plug a hole in the current implementation, we
use the older implementation as a hook to get the newer implementation
to run.

Later on, this can serve as a guide for how to implement the lower level
bits inside the scrapers themselves. For now, it just enables support.

* Plug the remaining scraper functions for now

Since we would like to have a scraper which works in between refactors,
plug the lower level parts of the scraper for now. It avoids us having
to tackle this part just yet.

* Move postprocessing to its own file

There's enough postprocessing to clutter the main scrapers.go file.

Move all of this into a new file, postprocessing to make the API
simpler. It now lives in scrapers.go.

* Scraper: Invoke API consistency

scraper.Cache.ScrapeByName -> ScrapeName

* Fix scraping scenes by URL

Simple typo. While here, also make a single marshaller nil-aware.

* Introduce scraper groups, consolidate loadByURL

Rename `scraper_s` into `group`. A group is a group of scrapers with
the same identity. This corresponds to a single YAML file for a scraper
configuration. It defines a group which supports different types of
scraping contexts.

Move config into the group, and lift txnManager and globalConfig to
the group.

Because we now return models.ScrapedContent we can use interfaces to
get variance from the different underlying scrapers. Use a type
switch for the URL matcher candidates. And then again for the scrapers.

This consolidates all URL scraping paths into one.

While here, remove the urlMatcher interface which isn't needed. Also
clean up the remaining interfaces for url scraping and delete code
which has no purpose anymore.

* Consolidate fragment scraping in one code path

While here, abide the linters checks.

* Refactor loadByFragment

Give it the same treatment as loadByURL:

Step 1: find a scraperActionImpl which works for the data.
Step 2: use that to scrape

Most of this is simple analysis on the data at hand. It can be pushed
down further in a later commit, but for now we leave it here.

* Remove configScraper, autotag is a scraper

Remove the remains of the configScraper struct. It now lives on in the
group struct. Kill the remaining interfaces from the old implementation
while here.

Remove group.specification since it can now be handled by a simple
func call to spec().

Work through the autotag scraper. It now implements the scraper
interface, so it can be used as a scraper. This also simplifies the
autotag scraper quite a bit since it doens't have to implement a number
of unsupported func calls.

* Simplify the fragment scraper flow

* Pass the context

Eliminate a round of context.TODO() in the scraper code by passing
the calling context down into the subsystem. This will gracefully
allow for termination of remote calls if the client goes away for some
reason in GraphQL requests.

* Improve listScrapers in the schema

Support lists of types we accept.

* Be graceful on nil values in conversion

Supporting nil-values make the API more robust in the
case of partial results in a multi-scrape situation.

* Improve listScrapers: output at-most-once

Use the ID of a scraper to reduce the output set. If a scraper has
been included, don't include it again.

* Consolidate all API level errors into resolver.go
* Reorder files and functions:

scrapers.go -> cache.go:
    It almost contains nothing but the cache code.
    Move errors into scraper.go from here because
    It is a better place to have them living right now
group.go:
    All of the group structure. This can now go from
    scraper.go, making it more lean. Move group create
    from config_scraper to here.
config.go:
    Move the `(c config) spec()` call to here.
config_scraper.go:
    Empty file by now

* Name-update the scraper interfaces

Use 'via' rather than 'loadBy'.

The scrape happens via a given scrape method, so I think this is a nice
name for it.

* Rename scrapers for consistency.

While here, improve the error formatting, so different errors come
back differently.

* Nuke the freeones field from the GraphQL schema

* Fix autotag interfacing, refactor

The autotag scraper uses a pointer receiver, but the rest of the code
we use for scraping doesn't expect a pointer-receiver. Hence, to fix
the autotag scraper, we change it to be a value receiver, like the
rest of the code.

Fix: viaScene, and viaGallery.

While here, remove a couple of pointer-receiver methods which can be
trivially rewritten into plain functions.

* Protect against pointer interfaces

The underlying code can be a bit inconsistent in what it returns.
Introduce pointer-types in the postprocessing layer and handle them
accordingly for now. Once a better understanding of the lower levels
are understood, we can lift this.

* Move ErrConversion into the models package.

The conversion error pertains to the logic of converting models.
Because of this, it should move there, so it is centralized.

* Be consistent in scraper resolver error handling

If we have a static error

    Err = errors.New(..)

Then use it wrapped at the start:

    fmt.Errorf("%w: ...context...", Err)

This reads better.

While here, avoid using the underlying Atoi errors: they are verbose,
and like 99% of the time, the user know what is wrong from the input
string, so just give that back.

Also, remove the scraper id from the error contexts: it is implicit,
and the error wouldn't change if we used a different scraper, which
the error message would imply.

* Mark the list*Scrapers() API as deprecated

The same functionality is now present in listScrapers.

* Improve error formatting

Think about how each error is going to be used and tweak them to be
nicer.

* Return a sorted list of scrapers

This helps testing, it's closer to what we had, caches like stable data,
and it is easier for humans. It also makes the output stable, because
map iteration is randomized.

* Fix listScrapers calls to return in ID-order

Since we need the ordering to be by ID in all situations, it is easier
to just generalize the cache listScrapers call to support multiple
scraper types.

This avoids a de-dupe map up the chain, since every scraper is only
considered once. Sorting now happens in the cache listScrapers call.

Use this generalized function in all resolvers, which are now simple
passthroughs.

* Remove UpdateConfig from the scraper cache.

This isn't needed, so get rid of it.

* Pull a context into identify

Scraping scenes in the identify tasks now use a context from up the
call chain.

* Do not store the scraper cache in the resolver.

Scraper caches are updated through
manager.singleton•RefreshScraperCache, so we can't keep a pointer to
it in the resolver. Instead, solve this by adding a fetcher method to
the resolver type. This keeps it local to the resolver, while handling
the problem of updating caches in the configuration.
2021-11-19 10:55:34 +11:00
kermieisinthehouse
8d94392cfb Fix Vite issues (#2038)
* Fix environment vars in Vite
* Add types, remove process.env override
* Temporarily Remove Statigz / gzip
* Update ui/v2.5/src/core/createClient.ts

Co-authored-by: peolic <66393006+peolic@users.noreply.github.com>

* Update ui/v2.5/src/serviceWorker.ts

Co-authored-by: peolic <66393006+peolic@users.noreply.github.com>

* Ignore case rules in types
* Add windows js workaround

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-18 19:38:19 +11:00
dependabot[bot]
0687cd2ef0 Bump browserslist from 4.14.2 to 4.18.1 in /ui/v2.5 (#2037)
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.14.2 to 4.18.1.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.14.2...4.18.1)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-18 14:08:58 +11:00
kermieisinthehouse
a4e52d3130 Vite-based frontend builds (#1900)
* Remove image conversion, add gzip
* Add MacOS Environment options
2021-11-18 12:32:04 +11:00
peolic
23b7d63417 Fix incorrect message id (#2036) 2021-11-18 12:17:26 +11:00
kermieisinthehouse
d8fcc1ead5 (Automated!) Translation updates (#2018)
* Translated using Weblate (Spanish)

Currently translated at 85.4% (580 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Spanish)

Currently translated at 85.4% (580 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Italian)

Currently translated at 100.0% (679 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Spanish)

Currently translated at 85.7% (582 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

* Translated using Weblate (Italian)

Currently translated at 100.0% (679 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/it/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 79.9% (543 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 79.9% (543 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 80.5% (547 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 81.0% (550 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 81.5% (554 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 84.3% (573 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (679 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/en_GB/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 85.2% (579 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 86.0% (584 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (679 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/en_GB/

* Translated using Weblate (German)

Currently translated at 78.6% (534 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.6% (656 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (German)

Currently translated at 78.7% (535 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.7% (657 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (German)

Currently translated at 80.4% (546 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.2% (660 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.4% (675 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 97.4% (662 of 679 strings)

Translation: Stash/Stash Desktop Client
Translate-URL: https://translate.stashapp.cc/projects/stash/stash-desktop-client/es/

Co-authored-by: Test User <234@example.com>
Co-authored-by: failead0r <6@example.com>
Co-authored-by: BViking78 <5@example.com>
Co-authored-by: Weblate Admin <kermie@arcticpornvault.org>
Co-authored-by: Philipw99 <9@example.com>
Co-authored-by: Peter M <coceexo@protonmail.com>
2021-11-18 09:48:28 +11:00
kermieisinthehouse
8483c896e8 Add Translation section to Readme (#2025) [skip ci] 2021-11-17 12:00:41 +11:00
peolic
955083882e Only add image files to imageBox files slice (#2017)
* Only add image files to `imageBox` files slice
* Update Changelog
2021-11-17 11:58:49 +11:00
Still Hsu
89c7c022f6 i18n: Update zh-tw & refactor various strings (#1998)
* Fix incorrect tense in toast
* Rename create_entity to emphasize past tense
* Localize "Started XXX" toasts
* Localize new zh-tw texts
* Refactor "continue" into the "actions" group
2021-11-17 11:56:22 +11:00
dependabot[bot]
fa077517d4 Bump hosted-git-info from 2.8.8 to 2.8.9 in /ui/v2.5 (#2030)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

---
updated-dependencies:
- dependency-name: hosted-git-info
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 11:25:23 +11:00
dependabot[bot]
5a0d0b975e Bump trim-newlines from 3.0.0 to 3.0.1 in /ui/v2.5 (#2029)
Bumps [trim-newlines](https://github.com/sindresorhus/trim-newlines) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/sindresorhus/trim-newlines/releases)
- [Commits](https://github.com/sindresorhus/trim-newlines/commits)

---
updated-dependencies:
- dependency-name: trim-newlines
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:59:04 +11:00
dependabot[bot]
2425dcc2d8 Bump postcss from 7.0.35 to 7.0.36 in /ui/v2.5 (#2028)
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.35 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.35...7.0.36)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:58:37 +11:00
dependabot[bot]
0376e1245f Bump url-parse from 1.5.1 to 1.5.3 in /ui/v2.5 (#2027)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.3)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:58:19 +11:00
dependabot[bot]
e7fbb4df0c Bump ws from 5.2.2 to 5.2.3 in /ui/v2.5 (#1995)
Bumps [ws](https://github.com/websockets/ws) from 5.2.2 to 5.2.3.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/5.2.2...5.2.3)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:35:49 +11:00
dependabot[bot]
0044ea787f Bump path-parse from 1.0.6 to 1.0.7 in /ui/v2.5 (#1994)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:35:33 +11:00
dependabot[bot]
2e34479bcc Bump object-path from 0.11.5 to 0.11.8 in /ui/v2.5 (#1993)
Bumps [object-path](https://github.com/mariocasciaro/object-path) from 0.11.5 to 0.11.8.
- [Release notes](https://github.com/mariocasciaro/object-path/releases)
- [Commits](https://github.com/mariocasciaro/object-path/commits/v0.11.8)

---
updated-dependencies:
- dependency-name: object-path
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:35:17 +11:00
dependabot[bot]
fc1c5b1890 Bump tmpl from 1.0.4 to 1.0.5 in /ui/v2.5 (#1992)
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-17 10:33:20 +11:00
WithoutPants
04ecc298b9 Merge pull request #2013 from stashapp/master
Merge master back to develop for tag
2021-11-16 10:22:18 +11:00
WithoutPants
1096fe812e Merge pull request #2011 from stashapp/develop
Merge to master for v0.11.0 release
2021-11-16 09:38:54 +11:00
kermieisinthehouse
5f5cd03929 V0.11.0 User Survey (#1971) 2021-11-16 09:23:33 +11:00
7dJx1qP
aa50dbf5e2 add missing trace loglevel (#2007) 2021-11-15 12:26:49 +11:00
Goran Osim
7547a1dd39 Fix Link to Scraping Document (#1988) [skip ci] 2021-11-15 09:07:23 +11:00
InfiniteTF
c571687c99 Resolve performer/studio stashIDs for scraped scenes (#2006)
* Resolve performer/studio stashIDs for scraped scenes
* Check endpoint when matching stashids
2021-11-15 07:51:52 +11:00
InfiniteTF
2a5afecc77 Fix invalid baseurl if host header already contains port (#2005) 2021-11-15 07:50:42 +11:00
InfiniteTF
df7d1427d6 Fix entity creation bug during tag/performer scraping (#2004) 2021-11-15 07:50:08 +11:00
peolic
0d76fede84 Fix config path regression (#1996) 2021-11-13 08:05:05 +11:00
kermieisinthehouse
2c7e0f0571 Apply python path resolution to plugins (#1990)
* Symlink python3 to python
* Apply path resolution to plugins
2021-11-12 09:21:52 +11:00
kermieisinthehouse
a7ed0a7004 Bump viper version, fix nobrowser (#1991) 2021-11-12 09:21:04 +11:00
InfiniteTF
808202ba8a Fix performer tagger field updating (#1977)
* Fix performer tagger field updating
2021-11-11 11:34:46 +11:00
kermieisinthehouse
07dbc2236d Update README.md (#1985) [skip ci] 2021-11-10 16:10:07 +11:00
WithoutPants
225e17f710 Confirm empty stash paths (#1983) 2021-11-10 16:09:09 +11:00
Releck
d316aeca16 Fix identify defaults not returning by using main viper instance (#1979) 2021-11-10 09:32:47 +11:00
kermieisinthehouse
bd0de23459 Spiffy Readme (#1980) [skip ci] 2021-11-10 09:31:40 +11:00
7dJx1qP
bcd6d4fb46 Update scene filename parser input styling (#1956)
* update scene filename parser input styling
* update parser field rating classnames
2021-11-08 11:41:47 +11:00
Frix-416
ffc8043273 French translation (fr-FR) (#1967) 2021-11-08 11:41:05 +11:00
WithoutPants
49b2860909 Improve flag and environment config overrides (#1898)
* Separate overrides from config
* Don't allow changing overridden value
* Write default host and port to config file
* Use existing library value. Hide generated if set
2021-11-08 10:14:11 +11:00
仇柯人
60c4f7e992 change translation typo error (#1963)
change the wrong typo DLAN to DLNA
2021-11-08 08:34:33 +11:00
kermieisinthehouse
1f48a9ce95 Fix Scenes Detail tab string (#1966) 2021-11-08 08:07:41 +11:00
WithoutPants
3aa5f657bc Add display autoplay and playlist continue options (#1921)
* Add display autoplay and playlist continue options
* Include continue parameter in scene links
2021-11-07 09:55:51 +11:00
gitgiggety
25274e2596 Support Is (not) null for all multi criterions (#1785)
* Support Is (not) null for all multi criterions

Add support for the Is null and Is not null modifiers for all cases of
the MultiCriterionInput and HierarchicalMultiCriterionInput. This
partially overlaps the "X Count" filter which sometimes is available
(because it would be the same as "X Count equals 0" and "X Count greater
than 0") but this also enables it for other criterions like the "Parent
Studio" filter for studios or just the "Studios" filter for scenes /
images / galleries, the "Movies" filter for scenes etc.

* Don't crash UI on bad saved filter
* Add missing code for tag parent/child

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-07 09:34:33 +11:00
7dJx1qP
e961ba4459 Fix tag hierarchy validation (#1926)
* update tag hierarchy validation
* refactor MergeHierarchy
* update tag hierarchy error message
* rename tag hierarchy function
* add tag path to error message
* Rename EnsureHierarchy to ValidateHierarchy

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-07 09:33:46 +11:00
WithoutPants
5bb5f6f2ce Move scene post update hook to outside Identify transaction (#1953)
* Move update post hook call outside transaction
* Make Scene Movies resolver use read transaction
2021-11-06 09:58:52 +11:00
WithoutPants
dbfd92f9a8 Support StashIDs in scrape dialog (#1955) 2021-11-05 12:10:29 +11:00
WithoutPants
392b28915a Fix exclusion pattern inputs blurring on every keypress (#1952)
* Replace ExclusionPatterns with StringListInput
2021-11-05 11:52:00 +11:00
liquid-flow
3671388b8d Rework main navbar (#1769)
* Fix responsive layout
* Refactor MainNavbar
* Stick the navbar to the bottom on mobile
* Fix menu item icon-text vertical alignment

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-04 18:12:48 +11:00
WithoutPants
602183cca9 File size scanning fixes (#1944) 2021-11-04 10:50:03 +11:00
stg-annon
206f86e304 Update and split up scraping docs (#1934) 2021-11-04 10:48:58 +11:00
kermieisinthehouse
9134490be2 Add linux ffmpeg urls (#1943) 2021-11-04 08:22:25 +11:00
7dJx1qP
a8a5d547ed Fix checkbox labels (#1947)
* fix create galleries from folders checkbox id
* add missing checkbox ids
2021-11-04 08:21:34 +11:00
7dJx1qP
ed9d80fcfc Add bottom pagination to scene duplicate checker (#1942)
* add bottom pagination to scene duplicate checker
* add top margin

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-11-03 09:22:44 +11:00
kermieisinthehouse
934d685e86 Small docker cleanup (#1941) 2021-11-02 11:39:53 +11:00
dependabot[bot]
32db1dda57 Bump github.com/tidwall/gjson from 1.8.1 to 1.9.3 (#1906)
Bumps [github.com/tidwall/gjson](https://github.com/tidwall/gjson) from 1.8.1 to 1.9.3.
- [Release notes](https://github.com/tidwall/gjson/releases)
- [Commits](https://github.com/tidwall/gjson/compare/v1.8.1...v1.9.3)

---
updated-dependencies:
- dependency-name: github.com/tidwall/gjson
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-01 11:31:28 +11:00
kermieisinthehouse
0dbee117d8 Update AutoTagging.md (#1935) 2021-11-01 11:17:32 +11:00
peolic
f4ac82f989 Update GridCard.tsx (#1930) 2021-11-01 11:16:58 +11:00
SmallCoccinelle
5f26719d2d Suppress a benign warning (#1932) 2021-11-01 11:16:23 +11:00
InfiniteTF
0d24af4cb4 Extend __typename stripper to fix interface config saving (#1929) 2021-11-01 11:15:03 +11:00
Still Hsu
07a1cdd9f7 Update zh-tw for Auto Identifcation Feature (#1923)
* Add localization strings for the new auto identification feature
2021-10-29 11:46:40 +11:00
7dJx1qP
c6a326ca64 Add stash_ids to performer, scene, studio import/export (#1916)
* add stash_ids to performer, scene, studio import/export
2021-10-29 11:12:39 +11:00
kermieisinthehouse
87036a07bc Start browser on server start (#1832)
* Start browser on server start
* Add config option for opening browser
2021-10-29 10:19:23 +11:00
7dJx1qP
29b14ab4fc move scrape-url-button style to component styles (#1925) 2021-10-29 09:08:43 +11:00
WithoutPants
ad7bb9a46f Clamp page size maximum (#1920) 2021-10-29 09:07:47 +11:00
7dJx1qP
b01c4468d8 Improve scrape url button UX (#1903)
* change button label to icon
* add URLField component
2021-10-28 17:22:26 +11:00
WithoutPants
bf59028bcd Bump go cache (#1919) 2021-10-28 17:00:24 +11:00
7dJx1qP
96fce90cc3 Add delete file and generated files by default config options (#1852)
* add delete file and generated files by default config options
* add alert message with files to be deleted

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-28 16:45:44 +11:00
WithoutPants
27cdefbb10 Identify task (#1839)
* Add identify task
* Change type naming
* Debounce folder select text input
* Add generic slice comparison function
2021-10-28 14:25:38 +11:00
WithoutPants
0f64954e5b Identify task (#1839)
* Add identify task
* Change type naming
* Debounce folder select text input
* Add generic slice comparison function
2021-10-28 14:25:17 +11:00
InfiniteTF
c93b5e12b7 Update typescript and eslint config (#1878)
* Update eslint rules
* Update typescript to 4.4
* Disable react/display-name
* Add @typescript-eslint/typescript-estree
2021-10-28 10:27:26 +11:00
WithoutPants
372ea7218e Fix UI crash when no scene phash (#1908) 2021-10-27 18:09:12 +11:00
7dJx1qP
b2897d0cf5 fix studio-details classname typo (#1907) 2021-10-27 16:21:49 +11:00
kermieisinthehouse
5de06d4b62 Convert filesize to float64 for arm32 (#1895)
* Convert filesize to float64 for arm32
2021-10-27 11:55:11 +11:00
WithoutPants
2136ced25c Use a default stash-box name if none provided (#1889)
* Use a default stash-box name if none provided
2021-10-27 11:54:55 +11:00
stashcoder42
bdb8dc94d3 Add page titles using react-helmet (#1831)
* add titles with react-helmet
2021-10-27 09:37:18 +11:00
WithoutPants
e9c7b0aed3 Fix new values not removed correctly when added (#1890)
* Fix new values not removed correctly when added
2021-10-27 09:33:08 +11:00
WithoutPants
a822455a66 Support create missing scraped movie studio (#1899)
* Support create missing scraped movie studio
2021-10-27 08:31:24 +11:00
InfiniteTF
1fffc0519a Split out entity creation from view pages (#1884)
* Split performerCreate page into separate page
* Split studioCreate into a separate page
* Remove Partial types from performer/studio
* Split tagCreate into a separate page
* Split movieCreate into a separate page
* Split out galleryCreate into its own page
* Add loader to scene page
* Fix performer name fallback
* Fix movie layout shift
* Fix prompt comment and switch studio prompt to localized string
2021-10-26 09:43:45 +11:00
kermieisinthehouse
c8182bdb4c Improve Spanish translation (#1894)
* Improve spanish translation
* Change scraper verbiage
2021-10-26 09:12:59 +11:00
kermieisinthehouse
35e646b68b Update italian translation (#1891) 2021-10-26 09:12:09 +11:00
WithoutPants
595e8efb73 Fix race condition in generate task (#1888) 2021-10-26 08:41:40 +11:00
kermieisinthehouse
1e5889ba17 Spanish translation (#1886)
* Spanish translation
2021-10-25 13:07:16 +11:00
kermieisinthehouse
4dd56c3d82 Show duration and filesize in results (#1776)
* Add new query interface
* Refactor query builder
* Change Query interface
* Return duration and filesize in scene query
* Adjust UI for scene metadata
* Introduce new image query interface
* Change image Query interface
* Add megapixels and size to image query
* Update image UI

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-25 11:40:13 +11:00
Still Hsu
1b411e3f43 i18n: Localize Setup & Migrate (#1880)
* Add migration & setup localization strings
* Update hard strings in tag panels
2021-10-25 10:59:03 +11:00
InfiniteTF
db1e5c63d0 Fix performer tagger (#1874)
* Fix performer tagger
2021-10-25 10:49:37 +11:00
kermieisinthehouse
cb52eb798d Italian Translation (it-IT) (#1875)
* Italian translation metadata
* Add json file from BViking
2021-10-25 10:41:29 +11:00
kermieisinthehouse
f904c59532 Fix docker (#1883) 2021-10-25 10:36:36 +11:00
SmallCoccinelle
401fc290ef Handle request cancellation more gracefully (#1877)
The version checking code performs its own error management and will
not pass errors to the caller. Hence, it needs to be aware of the types
of errors which can be returned.

In particular, the context.Canceled error will be returned if the
context is aborted through cancelation. This happens when the request
is terminated by tapping CTRL-C or if the browser request is terminated
while we are sitting waiting for the GH API.
2021-10-25 10:09:20 +11:00
WithoutPants
d292ed0b34 Fix deleting folder-based gallery file deleting (#1872)
* Fix deleting gallery image files
* Clarify delete file documentation
2021-10-25 09:29:03 +11:00
WithoutPants
0ab8d32687 Refactor generate task (#1871)
* Refactor generate task
2021-10-23 10:48:42 +11:00
kermieisinthehouse
f80a5e3222 Docker+Build optimizations (#1833)
* Docker CI builds: half the size, less than half the build time
* Add an "Official Build" Designator
* Fix .git constantly invalidating build cache, use distro ffmpeg
* Fix official build detection, add some compiler image docs

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-22 13:14:08 +11:00
kermieisinthehouse
7732152c0c Significantly speed up UI build by leveraging esbuild (#1851)
* Use esbuild wherever possible in ui build
2021-10-20 17:37:17 +11:00
InfiniteTF
15acf91b90 Add PHash distance matching to stash-box integration (#1858)
* Add PHash distance matching to stash-box integration
2021-10-20 17:22:25 +11:00
WithoutPants
976038424b Fix parsing ipv6 address with scope id (#1841)
* Fix parsing ipv6 address with scope id

Also allows connections from link local unicast address

* Add unit tests
2021-10-20 16:52:15 +11:00
SmallCoccinelle
8b7720e3bf Enable the appendAssign lint check (#1865)
* Make copies of buffers

Avoid reusing one of the incoming arrays as a append extension, and
make a copy of the data. It's cleaner in the long run and possibly
easier for the GC to maintain.

* Avoid appendAssign problems in tag code

Reuse the existing slice when appending.

* Fix appendAssign in encoder_scene_preview_chunk

Appending and creating a new slice is somewhat unintuitive since the
underlying slice might be extended to satisfy the new capacity. This
sometimes leads to faulty logic.

Rewrite the code so it reuses `args` for all appending, and builds one
array clearly in the code. It follows the general style of the function
where `args` is being built in small incremental batches and avoids
the introduction of new names.

* Enable the appendAssign check

This makes us pass all gocritic warnings.

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-20 16:34:19 +11:00
SmallCoccinelle
e513b6ffa5 Cache and reuse the scraper HTTP client (#1855)
* Add Cookies directly to the request

Rather than maintaining a cookie jar on a one-shot HTTP client, maintain
the jar ourselves: make a new jar, then use it to select the right
cookies.

The cookies are set on the request rather than on the client. This will
retain the current behavior as we are always throwing the client away
after each use.

This patch enables the lifting of the http client as well over time.

* Introduce a cached scraper HTTP client

The scraper cache is augmented with an *http.Client. These are safe for
concurrent use, so the pointer can safely be passed around. Push this
into scraper configurations where applicable, next to the txnManagers.

When we issue a loadUrl request, do so on the cached *http.Client,
which will reuse existing idle connections in the client if any are
present.

* Set MaxIdleConnsPerHost. Closes #1850

We allow for up to 8 idle connections to a single host. This should
make concurrent operation toward the same host reuse connections, even
for sizeable concurrency.

The number isn't bumped excessively high. We should probably limit
concurrency toward a single site anyway, since we'll be able to overrun
a site with queries quite easily if we have many concurrent goroutines
issuing requests at the same time.

* Reinstate driverOptions / useCDP check

Use DeMorgan's laws to invert the logic and exit early. Fixes tests
breaking.

* Documentation fixup.

* Use the scraper http.Client when fetching images

Fold image fetchers onto the cached scraper http.Client as well. This
makes the scraper have a single http.Client cache for all its
operations.

Thread the client upwards to the relevant attachment points: either the
cache, or a stash_box instance, which is extended to include a pointer
to the client.

Style roughly follows that of txnManagers.

* Use the same http Client as the GraphQL client use

Rather than using http.DefaultClient, use the same client as the
GraphQL client use in the stash_box subsystem. This localizes the
client used in the subsystem into the constructing New.. call.

* Hoist HTTP client construction

Create a function for initializaing the HTTP Client we use. While here
hoist magic numbers into constants. Introduce a proper static redirect
error and use it in the client code as well.

* Reinstate printCookies

This is a debugging function, and it might still come in handy in the
future at some point.

* Nitpick comment.

* Minor tidy

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-20 16:12:24 +11:00
SmallCoccinelle
214a15bc40 Fixups + enable the commentFormatting linter (#1866)
* Add a space after // comments

For consistency, the commentFormatting lint checker suggests a space
after each // comment block. This commit handles all the spots in
the code where that is needed.

* Rewrite documentation on functions

Use the Go idiom of commenting:

* First sentence declares the purpose.
* First word is the name being declared

The reason this style is preferred is such that grep is able to find
names the user might be interested in. Consider e.g.,

    go doc -all pkg/ffmpeg | grep -i transcode

in which case a match will tell you the name of the function you are
interested in.

* Remove old code comment-blocks

There are some commented out old code blocks in the code base. These are
either 3 years old, or 2 years old. By now, I don't think their use is
going to come back any time soon, and Git will track old pieces of
deleted code anyway.

Opt for deletion.

* Reorder imports

Split stdlib imports from non-stdlib imports in files we are touching.

* Use a range over an iteration variable

Probably more go-idiomatic, and the code needed comment-fixing anyway.

* Use time.After rather than rolling our own

The idiom here is common enough that the stdlib contains a function for
it. Use the stdlib function over our own variant.

* Enable the commentFormatting linter
2021-10-20 16:10:46 +11:00
SmallCoccinelle
e14bb8432c Enable gocritic (#1848)
* Don't capitalize local variables

ValidCodecs -> validCodecs

* Capitalize deprecation markers

A deprecated marker should be capitalized.

* Use re.MustCompile for static regexes

If the regex fails to compile, it's a programmer error, and should be
treated as such. The regex is entirely static.

* Simplify else-if constructions

Rewrite

   else { if cond {}}

to

   else if cond {}

* Use a switch statement to analyze formats

Break an if-else chain. While here, simplify code flow.

Also introduce a proper static error for unsupported image formats,
paving the way for being able to check against the error.

* Rewrite ifElse chains into switch statements

The "Effective Go" https://golang.org/doc/effective_go#switch document
mentions it is more idiomatic to write if-else chains as switches when
it is possible.

Find all the plain rewrite occurrences in the code base and rewrite.
In some cases, the if-else chains are replaced by a switch scrutinizer.
That is, the code sequence

  if x == 1 {
      ..
  } else if x == 2 {
      ..
  } else if x == 3 {
      ...
  }

can be rewritten into

  switch x {
  case 1:
    ..
  case 2:
    ..
  case 3:
    ..
  }

which is clearer for the compiler: it can decide if the switch is
better served by a jump-table then a branch-chain.

* Rewrite switches, introduce static errors

Introduce two new static errors:

* `ErrNotImplmented`
* `ErrNotSupported`

And use these rather than forming new generative errors whenever the
code is called. Code can now test on the errors (since they are static
and the pointers to them wont change).

Also rewrite ifElse chains into switches in this part of the code base.

* Introduce a StashBoxError in configuration

Since all stashbox errors are the same, treat them as such in the code
base. While here, rewrite an ifElse chain.

In the future, it might be beneifical to refactor configuration errors
into one error which can handle missing fields, which context the error
occurs in and so on. But for now, try to get an overview of the error
categories by hoisting them into static errors.

* Get rid of an else-block in transaction handling

If we succesfully `recover()`, we then always `panic()`. This means the
rest of the code is not reachable, so we can avoid having an else-block
here.

It also solves an ifElse-chain style check in the code base.

* Use strings.ReplaceAll

Rewrite

    strings.Replace(s, o, n, -1)

into

    strings.ReplaceAll(s, o, n)

To make it consistent and clear that we are doing an all-replace in the
string rather than replacing parts of it. It's more of a nitpick since
there are no implementation differences: the stdlib implementation is
just to supply -1.

* Rewrite via gocritic's assignOp

Statements of the form

    x = x + e

is rewritten into

    x += e

where applicable.

* Formatting

* Review comments handled

Stash-box is a proper noun.

Rewrite a switch into an if-chain which returns on the first error
encountered.

* Use context.TODO() over context.Background()

Patch in the same vein as everything else: use the TODO() marker so we
can search for it later and link it into the context tree/tentacle once
it reaches down to this level in the code base.

* Tell the linter to ignore a section in manager_tasks.go

The section is less readable, so mark it with a nolint for now. Because
the rewrite enables a ifElseChain, also mark that as nolint for now.

* Use strings.ReplaceAll over strings.Replace

* Apply an ifElse rewrite

else { if .. { .. } } rewrite into else if { .. }

* Use switch-statements over ifElseChains

Rewrite chains of if-else into switch statements. Where applicable,
add an early nil-guard to simplify case analysis. Also, in
ScanTask's Start(..), invert the logic to outdent the whole block, and
help the reader: if it's not a scene, the function flow is now far more
local to the top of the function, and it's clear that the rest of the
function has to do with scene management.

* Enable gocritic on the code base.

Disable appendAssign for now since we aren't passing that check yet.

* Document the nolint additions

* Document StashBoxBatchPerformerTagInput
2021-10-18 14:12:40 +11:00
Still Hsu
3e6e830f45 Update zh-tw Localization & Fix i18n Grammar Issue (#1807)
* Refactor parent/child strings
* Add new zh-tw strings

Signed-off-by: Still Hsu <dev@stillu.cc>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2021-10-16 09:56:46 +11:00
7dJx1qP
95e11d4aa8 Add clear button to list filter search input (#1845)
* add clear button to list filter search input
2021-10-15 14:42:32 +11:00
7dJx1qP
cf43a825d8 Add performer star ratings to main view (#1844)
* add performer star ratings to main view
* remove ratings from details and edit tab. move ratings hotkeys from edit tab to main view
2021-10-15 14:17:40 +11:00
kermieisinthehouse
5ec70ac3e0 Fix List filter styles, fix freeones spam (#1853)
* Fix List filter styles, fix freeones spam
2021-10-15 14:02:49 +11:00
WithoutPants
39fdde273d Scan refactor (#1816)
* Add file scanner
* Scan scene changes
* Split scan files
* Generalise scan
* Refactor ffprobe
* Refactor ffmpeg encoder
* Move scene scan code to scene package
* Move matchExtension to utils
* Refactor gallery scanning
* Refactor image scanning
* Prevent race conditions on identical hashes
* Refactor image thumbnail generation
* Perform count concurrently
* Allow progress increment before total set
* Make progress updates more frequent
2021-10-15 10:39:48 +11:00
WithoutPants
3d5ee16e90 Fix colours on console when logging to file (#1846)
* Fix colours on console when logging to file
2021-10-15 10:38:32 +11:00
7dJx1qP
1152e1acac Fix performer icon link padding (#1843)
* fix performer url icon link padding
2021-10-15 08:56:41 +11:00
7dJx1qP
602a32bd32 Add id to hierarchy filter checkbox input (#1842)
* add id to hierarchy filter checkbox input
2021-10-15 08:55:52 +11:00
SmallCoccinelle
655d3ae969 Toward better context handling (#1835)
* Use the request context

The code uses context.Background() in a flow where there is a
http.Request. Use the requests context instead.

* Use a true context in the plugin example

Let AddTag/RemoveTag take a context and use that context throughout
the example.

* Avoid the use of context.Background

Prefer context.TODO over context.Background deep in the call chain.

This marks the site as something which we need to context-handle
later, and also makes it clear to the reader that the context is
sort-of temporary in the code base.

While here, be consistent in handling the `act` variable in each
branch of the if .. { .. } .. check.

* Prefer context.TODO over context.Background

For the different scraping operations here, there is a context
higher up the call chain, which we ought to use. Mark the call-sites
as TODO for now, so we can come back later on a sweep of which parts
can be context-lifted.

* Thread context upwards

Initialization requires context for transactions. Thread the context
upward the call chain.

At the intialization call, add a context.TODO since we can't break this
yet. The singleton assumption prevents us from pulling it up into main for
now.

* make tasks context-aware

Change the task interface to understand contexts.

Pass the context down in some of the branches where it is needed.

* Make QueryStashBoxScene context-aware

This call naturally sits inside the request-context. Use it.

* Introduce a context in the JS plugin code

This allows us to use a context for HTTP calls inside the system.

Mark the context with a TODO at top level for now.

* Nitpick error formatting

Use %v rather than %s for error interfaces.
Do not begin an error strong with a capital letter.

* Avoid the use of http.Get in FFMPEG download chain

Since http.Get has no context, it isn't possible to break out or have
policy induced. The call will block until the GET completes. Rewrite
to use a http Request and provide a context.

Thread the context through the call chain for now. provide
context.TODO() at the top level of the initialization chain.

* Make getRemoteCDPWSAddress aware of contexts

Eliminate a call to http.Get and replace it with a context-aware
variant.

Push the context upwards in the call chain, but plug it before the
scraper interface so we don't have to rewrite said interface yet.

Plugged with context.TODO()

* Scraper: make the getImage function context-aware

Use a context, and pass it upwards. Plug it with context.TODO()
up the chain before the rewrite gets too much out of hand for now.

Minor tweaks along the way, remove a call to context.Background()
deep in the call chain.

* Make NOTIFY request context-aware

The call sits inside a Request-handler. So it's natural to use the
requests context as the context for the outgoing HTTP request.

* Use a context in the url scraper code

We are sitting in code which has a context, so utilize it for the
request as well.

* Use a context when checking versions

When we check the version of stash on Github, use a context. Thread
the context up to the initialization routine of the HTTP/GraphQL
server and plug it with a context.TODO() for now.

This paves the way for providing a context to the HTTP server code in a
future patch.

* Make utils func ReadImage context-aware

In almost all of the cases, there is a context in the call chain which
is a natural use. This is true for all the GraphQL mutations.

The exception is in task_stash_box_tag, so plug that task with
context.TODO() for now.

* Make stash-box get context-aware

Thread a context through the call chain until we hit the Client API.
Plug it with context.TODO() there for now.

* Enable the noctx linter

The code is now free of any uncontexted HTTP request. This means we
pass the noctx linter, and we can enable it in the code base.
2021-10-14 15:32:41 +11:00
SmallCoccinelle
41a1fb8aec Add collation to directory listings (#1823)
* Add collation to directory listings. Closes #1806

Introduce a new `locale` arg to the `Query.directory` field. Set "en"
as the default for the field for backward compatibility. Use the given
locale, sending it through a language matcher, and use `x/text` as the
collation engine for the matched language.

Augment the file `ListDirs` call to optionally take a Collator. If the
Collator is given, sort file listings according to the collators rules.

While here, document the GraphQL schema a bit more.

Add matchers by looking at the current front-end locales, and make sure
each of these occur in the matcher list.

* Language matcher touchups

* Avoid having `en-US` twice.
* Introduce `en-AU`.

* Pass IgnoreCase and Numeric collation

Allow the collator to be configured with options. Pass the options
IgnoreCase and Numeric to the collator.
2021-10-14 15:16:45 +11:00
WithoutPants
4eeef22c15 Generalise tagger view to all scraping sources (#1812)
* Refactor Tagger view
* Support link to existing studio/performer
* Allow stash id field selection
2021-10-14 08:53:04 +11:00
kermieisinthehouse
1217f3fbc1 add status badge (#1834) 2021-10-12 19:41:35 +11:00
SmallCoccinelle
c6f6205e4f Errorlint sweep + minor linter tweaks (#1796)
* Replace error assertions with Go 1.13 style

Use `errors.As(..)` over type assertions. This enables better use of
wrapped errors in the future, and lets us pass some errorlint checks
in the process.

The rewrite is entirely mechanical, and uses a standard idiom for
doing so.

* Use Go 1.13's errors.Is(..)

Rather than directly checking for error equality, use errors.Is(..).

This protects against error wrapping issues in the future.

Even though something like sql.ErrNoRows doesn't need the wrapping, do
so anyway, for the sake of consistency throughout the code base.

The change almost lets us pass the `errorlint` Go checker except for
a missing case in `js.go` which is to be handled separately; it isn't
mechanical, like these changes are.

* Remove goconst

goconst isn't a useful linter in many cases, because it's false positive
rate is high. It's 100% for the current code base.

* Avoid direct comparison of errors in recover()

Assert that we are catching an error from recover(). If we are,
check that the error caught matches errStop.

* Enable the "errorlint" checker

Configure the checker to avoid checking for errorf wraps. These are
often false positives since the suggestion is to blanket wrap errors
with %w, and that exposes the underlying API which you might not want
to do.

The other warnings are good however, and with the current patch stack,
the code base passes all these checks as well.

* Configure rowserrcheck

The project uses sqlx. Configure rowserrcheck to include said package.

* Mechanically rewrite a large set of errors

Mechanically search for errors that look like

    fmt.Errorf("...%s", err.Error())

and rewrite those into

    fmt.Errorf("...%v", err)

The `fmt` package is error-aware and knows how to call err.Error()
itself.

The rationale is that this is more idiomatic Go; it paves the
way for using error wrapping later with %w in some sites.

This patch only addresses the entirely mechanical rewriting caught by
a project-side search/replace. There are more individual sites not
addressed by this patch.
2021-10-12 14:03:08 +11:00
SmallCoccinelle
38384f2c60 Improve oshash code (#1829)
Reduce allocations. Don't create intermediary arrays which we then
consume right after. Manually fuse the arrays and decode straight into
the sum instead.

Furthermore, don't invoke a Reader, but carve out the locations via a
loop, directly.

These two changes taken together speeds up oshash computations by a
factor of 10 according to the benchmark tests. The main reason for
this change is a much lowered memory allocation rate which in turn
improves GC pressure.

While here, add a benchmark for oshash computations and use it for
testing the performance.
2021-10-12 11:59:51 +11:00
WithoutPants
e9d48683f8 Autotag scraper (#1817)
* Refactor scraper structures
* Move matching code into new package
* Add autotag scraper
* Always check first letter of auto-tag names
* Account for nulls

Co-authored-by: Kermie <kermie@isinthe.house>
2021-10-11 23:06:06 +11:00
WithoutPants
b5381ff071 Add option to disable create from dropdown (#1814)
* Convert config hooks to common context
* Add option to disable creating from dropdown
2021-10-11 17:45:58 +11:00
WithoutPants
46ae4581b8 Refactor clean task (#1805)
* Refactor clean task
* Use path sorting for scenes and galleries
* Add changelog
2021-10-11 16:45:24 +11:00
WithoutPants
94e8abcb50 Merge pull request #1827 from stashapp/master
Merge master to develop for version tag
2021-10-11 15:33:23 +11:00
1896 changed files with 249491 additions and 66457 deletions

View File

@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: "[Bug Report] Short Form Subject (50 Chars or less)"
labels: help wanted
labels: bug report
assignees: ''
---

View File

@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: "[Feature] Short Form Title (50 chars or less.)"
labels: enhancement
labels: feature request
assignees: ''
---

View File

@@ -2,9 +2,8 @@ name: Build
on:
push:
branches: [ develop, master ]
branches: [ develop, master, files-refactor ]
pull_request:
branches: [ develop ]
release:
types: [ published ]
@@ -13,7 +12,7 @@ concurrency:
cancel-in-progress: true
env:
COMPILER_IMAGE: stashapp/compiler:5
COMPILER_IMAGE: stashapp/compiler:6
jobs:
build:
@@ -47,15 +46,18 @@ jobs:
- name: Cache go build
uses: actions/cache@v2
env:
cache-name: cache-go-cache
# increment the number suffix to bump the cache
cache-name: cache-go-cache-1
with:
path: .go-cache
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/go.sum') }}
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('go.mod', '**/go.sum') }}
- name: Start build container
env:
official-build: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/develop') || (github.event_name == 'release' && github.ref != 'refs/tags/latest_develop') }}
run: |
mkdir -p .go-cache
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated -w /stash $COMPILER_IMAGE tail -f /dev/null
docker run -d --name build --mount type=bind,source="$(pwd)",target=/stash,consistency=delegated --mount type=bind,source="$(pwd)/.go-cache",target=/root/.cache/go-build,consistency=delegated --env OFFICIAL_BUILD=${{ env.official-build }} -w /stash $COMPILER_IMAGE tail -f /dev/null
- name: Pre-install
run: docker exec -t build /bin/bash -c "make pre-ui"
@@ -70,13 +72,8 @@ jobs:
# Static validation happens in the linter workflow in parallel to this workflow
# Run Dynamic validation here, to make sure we pass all the projects integration tests
#
# create UI file so that the embed doesn't fail
- name: Test Backend
run: |
mkdir -p ui/v2.5/build
touch ui/v2.5/build/index.html
docker exec -t build /bin/bash -c "make it"
run: docker exec -t build /bin/bash -c "make it"
- name: Build UI
# skip UI build for pull requests if UI is unchanged (UI was cached)
@@ -88,12 +85,12 @@ jobs:
- name: Compile for all supported platforms
run: |
docker exec -t build /bin/bash -c "make cross-compile-windows"
docker exec -t build /bin/bash -c "make cross-compile-osx-intel"
docker exec -t build /bin/bash -c "make cross-compile-osx-applesilicon"
docker exec -t build /bin/bash -c "make cross-compile-macos-intel"
docker exec -t build /bin/bash -c "make cross-compile-macos-applesilicon"
docker exec -t build /bin/bash -c "make cross-compile-linux"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm64v8"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm32v7"
docker exec -t build /bin/bash -c "make cross-compile-pi"
docker exec -t build /bin/bash -c "make cross-compile-linux-arm32v6"
- name: Cleanup build container
run: docker rm -f -v build
@@ -118,8 +115,8 @@ jobs:
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v2
with:
name: stash-osx
path: dist/stash-osx
name: stash-macos-intel
path: dist/stash-macos-intel
- name: Upload Linux binary
# only upload binaries for pull requests
@@ -133,6 +130,10 @@ jobs:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
run : git tag -f latest_develop; git push -f --tags
- name: Update files-refactor-release tag
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/files-refactor' }}
run : git tag -f files-refactor-release; git push -f --tags
- name: Development Release
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
uses: marvinpinto/action-automatic-releases@v1.1.2
@@ -142,34 +143,54 @@ jobs:
automatic_release_tag: latest_develop
title: "${{ env.STASH_VERSION }}: Latest development build"
files: |
dist/stash-osx
dist/stash-osx-applesilicon
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-pi
dist/stash-linux-arm32v6
CHECKSUMS_SHA1
- name: Master release
if: ${{ github.event_name == 'release' && github.ref != 'refs/tags/latest_develop' }}
uses: meeDamian/github-release@2.0
# NOTE: this isn't perfect, but should cover most scenarios
# DON'T create tag names starting with "v" if they are not stable releases
if: ${{ github.event_name == 'release' && !startsWith(github.ref, 'refs/tags/v') }}
uses: WithoutPants/github-release@v2.0.4
with:
token: "${{ secrets.GITHUB_TOKEN }}"
allow_override: true
files: |
dist/stash-osx
dist/stash-osx-applesilicon
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-pi
dist/stash-linux-arm32v6
CHECKSUMS_SHA1
gzip: false
- name: Files refactor Release
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/files-refactor' }}
uses: softprops/action-gh-release@v1
with:
token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: true
tag_name: files-refactor-release
target_commitish: refs/heads/files-refactor
files: |
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-linux-arm32v6
CHECKSUMS_SHA1
- name: Development Docker
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
if: ${{ github.repository == 'stashapp/stash' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
env:
DOCKER_CLI_EXPERIMENTAL: enabled
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
@@ -183,7 +204,9 @@ jobs:
bash ./docker/ci/x86_64/docker_push.sh development
- name: Release Docker
if: ${{ github.event_name == 'release' && github.ref != 'refs/tags/latest_develop' }}
# NOTE: this isn't perfect, but should cover most scenarios
# DON'T create tag names starting with "v" if they are not stable releases
if: ${{ github.repository == 'stashapp/stash' && github.event_name == 'release' && !startsWith(github.ref, 'refs/tags/v') }}
env:
DOCKER_CLI_EXPERIMENTAL: enabled
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}

View File

@@ -9,7 +9,7 @@ on:
pull_request:
env:
COMPILER_IMAGE: stashapp/compiler:5
COMPILER_IMAGE: stashapp/compiler:6
jobs:
golangci:
@@ -36,7 +36,7 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v1.42.1
version: v1.45.2
# Optional: working directory, useful for monorepos
# working-directory: somedir
@@ -51,10 +51,10 @@ jobs:
# skip-go-installation: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true
skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true
skip-build-cache: true
- name: Cleanup build container
run: docker rm -f -v build

3
.gitignore vendored
View File

@@ -30,6 +30,7 @@ ui/v2.5/src/core/generated-*.tsx
.idea/**/dictionaries
.idea/**/shelf
.vscode
.devcontainer
# Generated files
.idea/**/contentModel.xml
@@ -54,6 +55,6 @@ node_modules
*.db
stash
/stash
dist
.DS_Store

View File

@@ -17,30 +17,37 @@ linters:
- typecheck
- unused
- varcheck
# Linters added by the stash project
# - bodyclose
# Linters added by the stash project.
# - contextcheck
- dogsled
# - errorlint
- errchkjson
- errorlint
# - exhaustive
- exportloopref
# - goconst
# - gocritic
- gocritic
# - goerr113
- gofmt
# - gosec
# - gomnd
# - ifshort
- misspell
# - nakedret
# - noctx
# - paralleltest
- 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
@@ -79,4 +86,8 @@ linters-settings:
- name: unused-parameter
disabled: true
- name: unreachable-code
- name: redefines-builtin-id
- name: redefines-builtin-id
rowserrcheck:
packages:
- github.com/jmoiron/sqlx

View File

@@ -1,76 +0,0 @@
project_name: stash
before:
hooks:
- go mod download
builds:
- binary: stash-win
ldflags:
- "-extldflags '-static'"
env:
- CGO_ENABLED=1
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
flags:
- -tags
- extended
goos:
- windows
goarch:
- amd64
- binary: stash-osx
env:
- CGO_ENABLED=1
- CC=o64-clang
- CXX=o64-clang++
flags:
- -tags
- extended
goos:
- darwin
goarch:
- amd64
- binary: stash-osx-applesilicon
env:
- CGO_ENABLED=1
- CC=oa64-clang
- CXX=oa64-clang++
flags:
- -tags
- extended
goos:
- darwin
goarch:
- arm64
- binary: stash-linux
env:
- CGO_ENABLED=1
flags:
- -tags
- extended
goos:
- linux
goarch:
- amd64
archive:
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: "{{.ProjectName}}_{{.Version}}_{{.Os}}-{{.Arch}}"
replacements:
amd64: 64bit
386: 32bit
arm: ARM
arm64: ARM64
darwin: macOS
linux: Linux
windows: Windows
openbsd: OpenBSD
netbsd: NetBSD
freebsd: FreeBSD
dragonfly: DragonFlyBSD
files:
- README.md
- LICENSE
release:
draft: true

View File

@@ -1,13 +1,20 @@
model:
package: graphql
filename: ./pkg/scraper/stashbox/graphql/generated_models.go
client:
package: graphql
filename: ./pkg/scraper/stashbox/graphql/generated_client.go
models:
Date:
model: github.com/99designs/gqlgen/graphql.String
SceneDraftInput:
model: github.com/stashapp/stash/pkg/scraper/stashbox/graphql.SceneDraftInput
endpoint:
# This points to stashdb.org currently, but can be directed at any stash-box
# instance. It is used for generation only.
url: https://stashdb.org/graphql
query:
- "./graphql/stash-box/*.graphql"
generate:
clientV2: false
clientInterfaceName: "StashBoxGraphQLClient"

View File

@@ -1,117 +0,0 @@
if: tag != latest_develop # dont build for the latest_develop tagged version
dist: xenial
git:
depth: false
language: go
go:
- 1.17.x
services:
- docker
before_install:
- set -e
# Configure environment so changes are picked up when the Docker daemon is restarted after upgrading
- echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json
- export DOCKER_CLI_EXPERIMENTAL=enabled
# Upgrade to Docker CE 19.03 for BuildKit support
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
- sudo apt-get update
- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-ce
# install binfmt docker container, this container uses qemu to run arm programs transparently allowng docker to build arm 6,7,8 containers.
- docker run --rm --privileged docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
# Show info to simplify debugging and create a builder that can build the platforms we need
- docker info
- docker buildx create --name builder --use
- docker buildx inspect --bootstrap
- docker buildx ls
install:
- echo -e "machine github.com\n login $CI_USER_TOKEN" > ~/.netrc
- nvm install 12
- travis_retry make pre-ui
- make generate
- CI=false make ui-validate ui-only
#- go get -v github.com/mgechev/revive
script:
# left lint off to avoid getting extra dependency
#- make lint
- make fmt-check vet it
after_success:
- docker pull stashapp/compiler:5
- sh ./scripts/cross-compile.sh
- git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
- sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
- 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sh ./scripts/upload-pull-request.sh; fi'
before_deploy:
# push the latest tag when on the develop branch
- if [ "$TRAVIS_BRANCH" = "develop" ]; then git tag -f latest_develop; git push -f --tags; fi
- export RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z')
- export STASH_VERSION=$(git describe --tags --exclude latest_develop)
# set TRAVIS_TAG explcitly to the version so that it doesn't pick up latest_develop
- if [ "$TRAVIS_BRANCH" = "master" ]; then export TRAVIS_TAG=${STASH_VERSION}; fi
deploy:
# latest develop release
- provider: releases
# use the v2 release provider for proper release note setting
edge: true
api_key:
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
file:
- dist/stash-osx
- dist/stash-osx-applesilicon
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-linux-arm64v8
- dist/stash-linux-arm32v7
- dist/stash-pi
- CHECKSUMS_SHA1
skip_cleanup: true
overwrite: true
name: "${STASH_VERSION}: Latest development build"
release_notes: "**${RELEASE_DATE}**\n This is always the latest committed version on the develop branch. Use as your own risk!"
prerelease: true
on:
repo: stashapp/stash
branch: develop
# docker image build for develop release
- provider: script
skip_cleanup: true
script: bash ./docker/ci/x86_64/docker_push.sh development
on:
repo: stashapp/stash
branch: develop
# official master release - only build when tagged
- provider: releases
api_key:
secure: tGJ2q62CfPdayid2qEtW2aGRhMgCl3lBXYYQqp3eH0vFgIIf6cs7IDX7YC/x3XKMEQ/iMLZmtCXZvSTqNrD6Sk7MSnt30GIs+4uxIZDnnd8mV5X3K4n4gjD+NAORc4DrQBvUGrYMKJsR5gtkH0nu6diWb1o1If7OiJEuCPRhrmQYcza7NUdABnA9Z2wn2RNUV9Ga33WUCqLMEU5GtNBlfQPiP/khCQrqn/ocR6wUjYut3J6YagzqH4wsfJi3glHyWtowcNIw1LZi5zFxHD/bRBT4Tln7yypkjWNq9eQILA6i6kRUGf7ggyTx26/k8n4tnu+QD0vVh4EcjlThpU/LGyUXzKrrxjRwaDZnM0oYxg5AfHcBuAiAdo0eWnV3lEWRfTJMIVb9MPf4qDmzR4RREfB5OXOxwq3ODeCcJE8sTIMD/wBPZrlqS/QrRpND2gn2X4snkVukN9t9F4CMTFMtVSzFV7TDJW5E5Lq6VEExulteQhs6kcK9NRPNAaLgRQAw7X9kVWfDtiGUP+fE2i8F9Bo8bm7sOT5O5VPMPykx3EgeNg1IqIgMTCsMlhMJT4xBJoQUgmd2wWyf3Ryw+P+sFgdb5Sd7+lFgJBjMUUoOxMxAOiEgdFvCXcr+/Udyz2RdtetU1/6VzXzLPcKOw0wubZeBkISqu7o9gpfdMP9Eq00=
file:
- dist/stash-osx
- dist/stash-osx-applesilicon
- dist/stash-win.exe
- dist/stash-linux
- dist/stash-linux-arm64v8
- dist/stash-linux-arm32v7
- dist/stash-pi
- CHECKSUMS_SHA1
# make the release a draft so the maintainers can confirm before releasing
draft: true
skip_cleanup: true
overwrite: true
# don't write the body. To be done manually for now. In future we might
# want to generate the changelog or get it from a file
name: ${STASH_VERSION}
on:
repo: stashapp/stash
tags: true
# make sure we don't release using the latest_develop tag
condition: $TRAVIS_TAG != latest_develop
# docker image build for master release
- provider: script
skip_cleanup: true
script: bash ./docker/ci/x86_64/docker_push.sh latest
on:
repo: stashapp/stash
tags: true
# make sure we don't release using the latest_develop tag
condition: $TRAVIS_TAG != latest_develop

115
Makefile
View File

@@ -1,12 +1,12 @@
IS_WIN =
IS_WIN_SHELL =
ifeq (${SHELL}, sh.exe)
IS_WIN = true
IS_WIN_SHELL = true
endif
ifeq (${SHELL}, cmd)
IS_WIN = true
IS_WIN_SHELL = true
endif
ifdef IS_WIN
ifdef IS_WIN_SHELL
SEPARATOR := &&
SET := set
else
@@ -14,6 +14,11 @@ else
SET := export
endif
IS_WIN_OS =
ifeq ($(OS),Windows_NT)
IS_WIN_OS = true
endif
# set LDFLAGS environment variable to any extra ldflags required
# set OUTPUT to generate a specific binary name
@@ -41,9 +46,15 @@ ifndef STASH_VERSION
$(eval STASH_VERSION := $(shell git describe --tags --exclude latest_develop))
endif
ifndef OFFICIAL_BUILD
$(eval OFFICIAL_BUILD := false)
endif
build: pre-build
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/pkg/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/pkg/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/pkg/api.githash=$(GITHASH)')
go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)"
build:
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/internal/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/internal/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/internal/api.githash=$(GITHASH)')
$(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/internal/manager/config.officialBuild=$(OFFICIAL_BUILD)')
go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" $(GO_BUILD_FLAGS) -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS) $(PLATFORM_SPECIFIC_LDFLAGS)" ./cmd/stash
# strips debug symbols from the release build
build-release: EXTRA_LDFLAGS := -s -w
@@ -62,21 +73,40 @@ cross-compile-windows: export CXX := x86_64-w64-mingw32-g++
cross-compile-windows: OUTPUT := -o dist/stash-win.exe
cross-compile-windows: build-release-static
cross-compile-osx-intel: export GOOS := darwin
cross-compile-osx-intel: export GOARCH := amd64
cross-compile-osx-intel: export CC := o64-clang
cross-compile-osx-intel: export CXX := o64-clang++
cross-compile-osx-intel: OUTPUT := -o dist/stash-osx
cross-compile-macos-intel: export GOOS := darwin
cross-compile-macos-intel: export GOARCH := amd64
cross-compile-macos-intel: export CC := o64-clang
cross-compile-macos-intel: export CXX := o64-clang++
cross-compile-macos-intel: OUTPUT := -o dist/stash-macos-intel
# can't use static build for OSX
cross-compile-osx-intel: build-release
cross-compile-macos-intel: build-release
cross-compile-osx-applesilicon: export GOOS := darwin
cross-compile-osx-applesilicon: export GOARCH := arm64
cross-compile-osx-applesilicon: export CC := oa64e-clang
cross-compile-osx-applesilicon: export CXX := oa64e-clang++
cross-compile-osx-applesilicon: OUTPUT := -o dist/stash-osx-applesilicon
cross-compile-macos-applesilicon: export GOOS := darwin
cross-compile-macos-applesilicon: export GOARCH := arm64
cross-compile-macos-applesilicon: export CC := oa64e-clang
cross-compile-macos-applesilicon: export CXX := oa64e-clang++
cross-compile-macos-applesilicon: OUTPUT := -o dist/stash-macos-applesilicon
# can't use static build for OSX
cross-compile-osx-applesilicon: build-release
cross-compile-macos-applesilicon: build-release
cross-compile-macos:
rm -rf dist/Stash.app dist/Stash-macos.zip
make cross-compile-macos-applesilicon
make cross-compile-macos-intel
# Combine into one universal binary
lipo -create -output dist/stash-macos-universal dist/stash-macos-intel dist/stash-macos-applesilicon
rm dist/stash-macos-intel dist/stash-macos-applesilicon
# Place into bundle and zip up
cp -R scripts/macos-bundle dist/Stash.app
mkdir dist/Stash.app/Contents/MacOS
mv dist/stash-macos-universal dist/Stash.app/Contents/MacOS/stash
cd dist && zip -r Stash-macos.zip Stash.app && cd ..
rm -rf dist/Stash.app
cross-compile-freebsd: export GOOS := freebsd
cross-compile-freebsd: export GOARCH := amd64
cross-compile-freebsd: OUTPUT := -o dist/stash-freebsd
cross-compile-freebsd: build-release-static
cross-compile-linux: export GOOS := linux
cross-compile-linux: export GOARCH := amd64
@@ -96,21 +126,30 @@ cross-compile-linux-arm32v7: export CC := arm-linux-gnueabihf-gcc
cross-compile-linux-arm32v7: OUTPUT := -o dist/stash-linux-arm32v7
cross-compile-linux-arm32v7: build-release-static
cross-compile-pi: export GOOS := linux
cross-compile-pi: export GOARCH := arm
cross-compile-pi: export GOARM := 6
cross-compile-pi: export CC := arm-linux-gnueabi-gcc
cross-compile-pi: OUTPUT := -o dist/stash-pi
cross-compile-pi: build-release-static
cross-compile-linux-arm32v6: export GOOS := linux
cross-compile-linux-arm32v6: export GOARCH := arm
cross-compile-linux-arm32v6: export GOARM := 6
cross-compile-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
cross-compile-linux-arm32v6: OUTPUT := -o dist/stash-linux-arm32v6
cross-compile-linux-arm32v6: build-release-static
cross-compile-all:
make cross-compile-windows
make cross-compile-osx-intel
make cross-compile-osx-applesilicon
make cross-compile-macos
make cross-compile-linux
make cross-compile-linux-arm64v8
make cross-compile-linux-arm32v7
make cross-compile-pi
make cross-compile-linux-arm32v6
.PHONY: touch-ui
touch-ui:
ifndef IS_WIN_SHELL
@mkdir -p ui/v2.5/build
@touch ui/v2.5/build/index.html
else
@if not exist "ui\\v2.5\\build" mkdir ui\\v2.5\\build
@type nul >> ui/v2.5/build/index.html
endif
# Regenerates GraphQL files
generate: generate-backend generate-frontend
@@ -120,8 +159,8 @@ generate-frontend:
cd ui/v2.5 && yarn run gqlgen
.PHONY: generate-backend
generate-backend:
go generate -mod=vendor
generate-backend: touch-ui
go generate -mod=vendor ./cmd/stash
# Regenerates stash-box client files
.PHONY: generate-stash-box-client
@@ -160,17 +199,17 @@ pre-ui:
.PHONY: ui
ui: pre-build
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) REACT_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) REACT_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
$(SET) VITE_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) VITE_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) VITE_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
cd ui/v2.5 && yarn build
.PHONY: ui-start
ui-start: pre-build
$(SET) REACT_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) REACT_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) REACT_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
cd ui/v2.5 && yarn start
$(SET) VITE_APP_DATE="$(BUILD_DATE)" $(SEPARATOR) \
$(SET) VITE_APP_GITHASH=$(GITHASH) $(SEPARATOR) \
$(SET) VITE_APP_STASH_VERSION=$(STASH_VERSION) $(SEPARATOR) \
cd ui/v2.5 && yarn start --host
.PHONY: fmt-ui
fmt-ui:
@@ -195,5 +234,5 @@ validate-backend: lint it
# locally builds and tags a 'stash/build' docker image
.PHONY: docker-build
docker-build:
docker build -t stash/build -f docker/build/x86_64/Dockerfile .
docker-build: pre-build
docker build --build-arg GITHASH=$(GITHASH) --build-arg STASH_VERSION=$(STASH_VERSION) -t stash/build -f docker/build/x86_64/Dockerfile .

150
README.md
View File

@@ -1,82 +1,53 @@
# Stash
https://stashapp.cc
[![Build](https://github.com/stashapp/stash/actions/workflows/build.yml/badge.svg?branch=develop&event=push)](https://github.com/stashapp/stash/actions/workflows/build.yml)
[![Docker pulls](https://img.shields.io/docker/pulls/stashapp/stash.svg)](https://hub.docker.com/r/stashapp/stash 'DockerHub')
[![Go Report Card](https://goreportcard.com/badge/github.com/stashapp/stash)](https://goreportcard.com/report/github.com/stashapp/stash)
[![Discord](https://img.shields.io/discord/559159668438728723.svg?logo=discord)](https://discord.gg/2TsNFKt)
https://stashapp.cc
### **Stash is a self-hosted webapp written in Go which organizes and serves your porn.**
![demo image](docs/readme_assets/demo_image.png)
**Stash is a locally hosted web-based app written in Go which organizes and serves your porn.**
* It can gather information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers.
* It supports a wide variety of both video and image formats.
* Stash gathers information about videos in your collection from the internet, and is extensible through the use of community-built plugins for a large number of content producers and sites.
* Stash supports a wide variety of both video and image formats.
* You can tag videos and find them later.
* It provides statistics about performers, tags, studios and other things.
* Stash provides statistics about performers, tags, studios and more.
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
For further information you can [read the in-app manual](ui/v2.5/src/docs/en).
# Installing stash
# Installing Stash
## via Docker
Follow [this README.md in the docker directory.](docker/production/README.md)
## Pre-Compiled Binaries
The Stash server runs on macOS, Windows, and Linux. Download the [latest release here](https://github.com/stashapp/stash/releases).
Run the executable (double click the exe on windows or run `./stash-osx` / `./stash-linux` from the terminal on macOS / Linux) and navigate to either https://localhost:9999 or http://localhost:9999 to get started.
*Note for Windows users:* Running the app might present a security prompt since the binary isn't yet signed. Bypass this by clicking "more info" and then the "run anyway" button.
<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
:---:|:---:|:---:|:---:
[Latest Release](https://github.com/stashapp/stash/releases/latest/download/stash-win.exe) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/stash-win.exe)</sub></sup> | [Latest Release (Apple Silicon)](https://github.com/stashapp/stash/releases/latest/download/stash-macos-applesilicon) <br /> <sup><sub>[Development Preview (Apple Silicon)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-macos-applesilicon)</sub></sup> <br />[Latest Release (Intel)](https://github.com/stashapp/stash/releases/latest/download/stash-macos-intel) <br /> <sup><sub>[Development Preview (Intel)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-macos-intel)</sub></sup> | [Latest Release (amd64)](https://github.com/stashapp/stash/releases/latest/download/stash-linux) <br /> <sup><sub>[Development Preview (amd64)](https://github.com/stashapp/stash/releases/download/latest_develop/stash-linux)</sub></sup> <br /> [More Architectures...](https://github.com/stashapp/stash/releases/latest) | [Instructions](docker/production/README.md) <br /> <sup><sub> [Sample docker-compose.yml](docker/production/docker-compose.yml)</sub></sup>
## First Run
#### Windows Users: Security Prompt
Running the app might present a security prompt since the binary isn't yet signed. Bypass this by clicking "more info" and then the "run anyway" button.
#### FFMPEG
If stash is unable to find or download FFMPEG then download it yourself from the link for your platform:
* [macOS ffmpeg](https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip), [macOS ffprobe](https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip)
* [Windows](https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip)
* [Linux](https://www.johnvansickle.com/ffmpeg/)
The `ffmpeg(.exe)` and `ffprobe(.exe)` files should be placed in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows.
Stash requires ffmpeg. If you don't have it installed, Stash will download a copy for you. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
# Usage
## Quickstart Guide
1) Download and install Stash and its dependencies
2) Run Stash. It will prompt you for some configuration options and a directory to index (you can also do this step afterward)
3) After configuration, launch your web browser and navigate to the URL shown within the Stash app.
Stash is a web-based application. Once the application is running, the interface is available (by default) from http://localhost:9999.
**Note that Stash does not currently retrieve and organize information about your entire library automatically.** You will need to help it along through the use of [scrapers](blob/develop/ui/v2.5/src/docs/en/Scraping.md). The Stash community has developed scrapers for many popular data sources which can be downloaded and installed from [this repository](https://github.com/stashapp/CommunityScrapers).
On first run, Stash will prompt you for some configuration options and media directories to index, called "Scanning" in Stash. After scanning, your media will be available for browsing, curating, editing, and tagging.
The simplest way to tag a large number of files is by using the [Tagger](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Tagger.md) which uses filename keywords to help identify the file and pull in scene and performer information from our stash-box database. Note that this data source is not comprehensive and you may need to use the scrapers to identify some of your media.
Stash can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en/Scraping.md), which integrate directly into Stash.
## CLI
Many community-maintained scrapers are available for download at the [Community Scrapers Collection](https://github.com/stashapp/CommunityScrapers). The community also maintains StashDB, a crowd-sourced repository of scene, studio, and performer information, that can automatically identify much of a typical media collection. Inquire in the Discord for details. Identifying an entire collection will typically require a mix of multiple sources.
Stash runs as a command-line app and local web server. There are some command-line options available, which you can see by running `stash --help`.
<sub>StashDB is the canonical instance of our open source metadata API, [stash-box](https://github.com/stashapp/stash-box).</sub>
For example, to run stash locally on port 80 run it like this (OSX / Linux) `stash --host 127.0.0.1 --port 80`
# Translation
[![Translate](https://translate.stashapp.cc/widgets/stash/-/stash-desktop-client/svg-badge.svg)](https://translate.stashapp.cc/engage/stash/)
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇪🇸 🇸🇪 🇹🇼 🇹🇷
## SSL (HTTPS)
Stash can run over HTTPS with some additional work. First you must generate a SSL certificate and key combo. Here is an example using openssl:
`openssl req -x509 -newkey rsa:4096 -sha256 -days 7300 -nodes -keyout stash.key -out stash.crt -extensions san -config <(echo "[req]"; echo distinguished_name=req; echo "[san]"; echo subjectAltName=DNS:stash.server,IP:127.0.0.1) -subj /CN=stash.server`
This command would need customizing for your environment. [This link](https://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl) might be useful.
Once you have a certificate and key file name them `stash.crt` and `stash.key` and place them in the same directory as the `config.yml` file, or the `~/.stash` directory. Stash detects these and starts up using HTTPS rather than HTTP.
## Basepath rewriting
The basepath defaults to `/`. When running stash via a reverse proxy in a subpath, the basepath can be changed by having the reverse proxy pass `X-Forwarded-Prefix` (and optionally `X-Forwarded-Port`) headers. When detects these headers, it alters the basepath URL of the UI.
# Customization
## Themes and CSS Customization
There is a [directory of community-created themes](https://github.com/stashapp/stash/wiki/Themes) on our Wiki, along with instructions on how to install them.
You can also make Stash interface fit your desired style with [Custom CSS snippets](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets) and [CSS Tweaks](https://github.com/stashapp/stash/wiki/CSS-Tweaks).
Stash is available in 16 languages (so far!) and it could be in your language too. If you want to help us translate Stash into your language, you can make an account at [translate.stashapp.cc](https://translate.stashapp.cc/projects/stash/stash-desktop-client/) to get started contributing new languages or improving existing ones. Thanks!
# Support (FAQ)
@@ -85,73 +56,18 @@ Answers to other Frequently Asked Questions can be found [on our Wiki](https://g
For issues not addressed there, there are a few options.
* Read the [Wiki](https://github.com/stashapp/stash/wiki)
* Check the in-app documentation (also available [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en)
* Check the in-app documentation, in the top right corner of the app (also available [here](https://github.com/stashapp/stash/tree/develop/ui/v2.5/src/docs/en)
* Join the [Discord server](https://discord.gg/2TsNFKt), where the community can offer support.
# Compiling From Source Code
# Customization
## Pre-requisites
## Themes and CSS Customization
There is a [directory of community-created themes](https://github.com/stashapp/stash/wiki/Themes) on our Wiki, along with instructions on how to install them.
* [Go](https://golang.org/dl/)
* [GolangCI](https://golangci-lint.run/) - A meta-linter which runs several linters in parallel
* To install, follow the [local installation instructions](https://golangci-lint.run/usage/install/#local-installation)
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2.5` folder (before running make generate for first time).
You can also make Stash interface fit your desired style with [Custom CSS snippets](https://github.com/stashapp/stash/wiki/Custom-CSS-snippets).
NOTE: You may need to run the `go get` commands outside the project directory to avoid modifying the projects module file.
# For Developers
## Environment
Pull requests are welcome!
### macOS
TODO
### Windows
1. Download and install [Go for Windows](https://golang.org/dl/)
2. Download and install [MingW](https://sourceforge.net/projects/mingw-w64/)
3. Search for "advanced system settings" and open the system properties dialog.
1. Click the `Environment Variables` button
2. Under system variables find the `Path`. Edit and add `C:\Program Files\mingw-w64\*\mingw64\bin` (replace * with the correct path).
NOTE: The `make` command in Windows will be `mingw32-make` with MingW.
## Commands
* `make generate` - Generate Go and UI GraphQL files
* `make build` - Builds the binary (make sure to build the UI as well... see below)
* `make docker-build` - Locally builds and tags a complete 'stash/build' docker image
* `make pre-ui` - Installs the UI dependencies. Only needs to be run once before building the UI for the first time, or if the dependencies are updated
* `make fmt-ui` - Formats the UI source code
* `make ui` - Builds the frontend
* `make lint` - Run the linter on the backend
* `make fmt` - Run `go fmt`
* `make it` - Run the unit and integration tests
* `make validate` - Run all of the tests and checks required to submit a PR
* `make ui-start` - Runs the UI in development mode. Requires a running stash server to connect to. Stash port can be changed from the default of `9999` with environment variable `REACT_APP_PLATFORM_PORT`.
## Building a release
1. Run `make generate` to create generated files
2. Run `make ui` to compile the frontend
3. Run `make build` to build the executable for your current platform
## Cross compiling
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
where the app can be cross-compiled. This process is kicked off by CI via the `scripts/cross-compile.sh` script. Run the following
command to open a bash shell to the container to poke around:
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashappdev/compiler:latest /bin/bash`
## Profiling
Stash can be profiled using the `--cpuprofile <output profile filename>` command line flag.
The resulting file can then be used with pprof as follows:
`go tool pprof <path to binary> <path to profile filename>`
With `graphviz` installed and in the path, a call graph can be generated with:
`go tool pprof -svg <path to binary> <path to profile filename> > <output svg file>`
See [Development](docs/DEVELOPMENT.md) and [Contributing](docs/CONTRIBUTING.md) for information on working with the codebase, getting a local development setup, and contributing changes.

68
cmd/stash/main.go Normal file
View File

@@ -0,0 +1,68 @@
//go:generate go run -mod=vendor github.com/99designs/gqlgen
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/stashapp/stash/internal/api"
"github.com/stashapp/stash/internal/desktop"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/ui"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
defer recoverPanic()
_, err := manager.Initialize()
if err != nil {
panic(err)
}
go func() {
defer recoverPanic()
if err := api.Start(); err != nil {
handleError(err)
} else {
manager.GetInstance().Shutdown(0)
}
}()
go handleSignals()
desktop.Start(manager.GetInstance(), &manager.FaviconProvider{UIBox: ui.UIBox})
blockForever()
}
func recoverPanic() {
if p := recover(); p != nil {
handleError(fmt.Errorf("Panic: %v", p))
}
}
func handleError(err error) {
if desktop.IsDesktop() {
desktop.FatalError(err)
manager.GetInstance().Shutdown(0)
} else {
panic(err)
}
}
func handleSignals() {
// handle signals
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
<-signals
manager.GetInstance().Shutdown(0)
}
func blockForever() {
select {}
}

View File

@@ -1,43 +1,40 @@
# This dockerfile must be built from the top-level stash directory
# ie from top-level stash:
# docker build -t stash/build -f docker/build/x86_64/Dockerfile .
# This dockerfile should be built with `make docker-build` from the stash root.
# Build Frontend
FROM node:alpine as frontend
RUN apk add --no-cache make git
RUN apk add --no-cache make
## cache node_modules separately
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
WORKDIR /stash
RUN yarn --cwd ui/v2.5 install --frozen-lockfile.
COPY Makefile /stash/
COPY ./.git /stash/.git
COPY ./graphql /stash/graphql/
COPY ./ui /stash/ui/
RUN make generate-frontend
ARG GITHASH
ARG STASH_VERSION
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
# Build Backend
FROM golang:1.17-alpine as backend
RUN apk add --no-cache xz make alpine-sdk
## install ffmpeg
WORKDIR /
RUN wget -O /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
tar xf /ffmpeg.tar.xz && \
rm ffmpeg.tar.xz && \
mv /ffmpeg*/ /ffmpeg/
RUN apk add --no-cache make alpine-sdk
WORKDIR /stash
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
COPY ./scripts /stash/scripts/
COPY ./vendor /stash/vendor/
COPY ./pkg /stash/pkg/
COPY ./cmd /stash/cmd
COPY ./internal /stash/internal
COPY --from=frontend /stash /stash/
RUN make generate-backend
ARG GITHASH
ARG STASH_VERSION
RUN make build
# Final Runnable Image
FROM alpine:latest
RUN apk add --no-cache ca-certificates vips-tools
COPY --from=backend /stash/stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
RUN apk add --no-cache ca-certificates vips-tools ffmpeg
COPY --from=backend /stash/stash /usr/bin/
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
EXPOSE 9999
ENTRYPOINT ["stash"]
ENTRYPOINT ["stash"]

View File

@@ -1,13 +1,13 @@
# Introduction
This dockerfile is used to build a stash docker container using the current source code.
This dockerfile is used to build a stash docker container using the current source code. This is ideal for testing your current branch in docker. Note that it does not include python, so python-based scrapers will not work in this image. The production docker images distributed by the project contain python and the necessary packages.
# Building the docker container
From the top-level directory (should contain `main.go` file):
```
docker build -t stash/build -f ./docker/build/x86_64/Dockerfile .
make docker-build
```

View File

@@ -1,22 +1,19 @@
FROM --platform=$BUILDPLATFORM ubuntu:20.04 AS prep
FROM --platform=$BUILDPLATFORM alpine:latest AS binary
ARG TARGETPLATFORM
WORKDIR /
COPY stash-* /
RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-pi; \
RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-linux-arm32v6; \
elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then BIN=stash-linux-arm32v7; \
elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then BIN=stash-linux-arm64v8; \
elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then BIN=stash-linux; \
fi; \
mv $BIN /stash
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install -y python3 python-is-python3 python3-requests python3-requests-toolbelt python3-lxml python3-pip && pip3 install cloudscraper
FROM ubuntu:20.04 as app
run apt update && apt install -y python3 python-is-python3 python3-requests python3-requests-toolbelt python3-lxml python3-mechanicalsoup ffmpeg libvips-tools && rm -rf /var/lib/apt/lists/*
COPY --from=prep /stash /usr/bin/
COPY --from=prep /usr/local/lib/python3.8/dist-packages /usr/local/lib/python3.8/dist-packages
FROM --platform=$TARGETPLATFORM alpine:latest AS app
COPY --from=binary /stash /usr/bin/
RUN apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg vips-tools && pip install --no-cache-dir mechanicalsoup cloudscraper
RUN ln -s /usr/bin/python3 /usr/bin/python
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
EXPOSE 9999
CMD ["stash"]

View File

@@ -10,5 +10,5 @@ done
echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
# must build the image from dist directory
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 --push $DOCKER_TAGS -f docker/ci/x86_64/Dockerfile dist/
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 --push $DOCKER_TAGS -f docker/ci/x86_64/Dockerfile dist/

View File

@@ -20,7 +20,7 @@ RUN apt-get update && \
gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \
gcc-arm-linux-gnueabihf libc-dev-armhf-cross \
gcc-aarch64-linux-gnu libc-dev-arm64-cross \
nodejs yarn --no-install-recommends || exit 1; \
nodejs yarn zip --no-install-recommends || exit 1; \
rm -rf /var/lib/apt/lists/*;
# Cross compile setup
@@ -48,10 +48,20 @@ RUN mkdir -p /root/.ssh; \
ssh-keyscan github.com > /root/.ssh/known_hosts;
# Notes for self:
# To test locally:
# make generate
# make ui
# cd docker/compiler
# make build
# docker run -it -v /PATH_TO_STASH:/go/stash stashapp/compiler:latest /bin/bash
# cd stash
# make cross-compile-all
# # binaries will show up in /dist
# Windows:
# GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags "-extldflags '-static'" -tags extended
# Darwin
# CC=o64-clang CXX=o64-clang++ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 go build -tags extended
# env goreleaser --config=goreleaser-extended.yml --skip-publish --skip-validate --rm-dist --release-notes=temp/0.48-relnotes-ready.md

View File

@@ -1,6 +1,6 @@
user=stashapp
repo=compiler
version=5
version=6
latest:
docker build -t ${user}/${repo}:latest .

View File

@@ -1,5 +1,3 @@
Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and `.travis.yml` needs to be updated to pull the correct image tag.
A MacOS univeral binary can be created using `lipo -create -output stash-osx-universal stash-osx stash-osx-applesilicon`, available in the image.
When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and the github workflow files need to be updated to pull the correct image tag.

View File

@@ -7,16 +7,6 @@ Only `docker` and `docker-compose` are required. For the most part your understa
Installation instructions are available below, and if your distrobution's repository ships a current version of docker, you may use that.
https://docs.docker.com/engine/install/
### Docker
Docker is effectively a cross-platform software package repository. It allows you to ship an entire environment in what's referred to as a container. Containers are intended to hold everything that is needed to run an application from one place to another, making it easy for everyone along the way to reproduce the environment.
The StashApp docker container ships with everything you need to automatically build and run stash, including ffmpeg.
### docker-compose
Docker Compose lets you specify how and where to run your containers, and to manage their environment. The docker-compose.yml file in this folder gets you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on. If you are deploying a live instance for production, a reverse proxy (such as NGINX or Traefik) is recommended, but not required.
The latest version is always recommended.
### Get the docker-compose.yml file
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
@@ -35,3 +25,13 @@ docker-compose up -d
Installing StashApp this way will by default bind stash to port 9999. This is available in your web browser locally at http://localhost:9999 or on your network as http://YOUR-LOCAL-IP:9999
Good luck and have fun!
### Docker
Docker is effectively a cross-platform software package repository. It allows you to ship an entire environment in what's referred to as a container. Containers are intended to hold everything that is needed to run an application from one place to another, making it easy for everyone along the way to reproduce the environment.
The StashApp docker container ships with everything you need to automatically build and run stash, including ffmpeg.
### docker-compose
Docker Compose lets you specify how and where to run your containers, and to manage their environment. The docker-compose.yml file in this folder gets you a fully working instance of StashApp exactly as you would need it to have a reasonable instance for testing / developing on. If you are deploying a live instance for production, a reverse proxy (such as NGINX or Traefik) is recommended, but not required.
The latest version is always recommended.

View File

@@ -1,27 +0,0 @@
FROM ubuntu:20.04 as prep
LABEL MAINTAINER="https://discord.gg/2TsNFKt"
RUN apt-get update && \
apt-get -y install curl xz-utils && \
apt-get autoclean -y && \
rm -rf /var/lib/apt/lists/*
WORKDIR /
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# added " to end of stash-linux clause so that it doesn't pick up the arm builds
RUN curl -L -o /stash $(curl -s https://api.github.com/repos/stashapp/stash/releases/latest | awk '/browser_download_url/ && /stash-linux/"' | sed -e 's/.*: "\(.*\)"/\1/') && \
chmod +x /stash
RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
tar xf /ffmpeg.tar.xz && \
rm ffmpeg.tar.xz && \
mv /ffmpeg*/ /ffmpeg/
FROM ubuntu:20.04 as app
RUN apt-get update && apt-get -y install ca-certificates
COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
EXPOSE 9999
CMD ["stash"]

68
docs/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,68 @@
# Building from Source
## Pre-requisites
* [Go](https://golang.org/dl/)
* [GolangCI](https://golangci-lint.run/) - A meta-linter which runs several linters in parallel
* To install, follow the [local installation instructions](https://golangci-lint.run/usage/install/#local-installation)
* [Yarn](https://yarnpkg.com/en/docs/install) - Yarn package manager
* Run `yarn install --frozen-lockfile` in the `stash/ui/v2.5` folder (before running make generate for first time).
NOTE: You may need to run the `go get` commands outside the project directory to avoid modifying the projects module file.
## Environment
### Windows
1. Download and install [Go for Windows](https://golang.org/dl/)
2. Download and extract [MingW64](https://sourceforge.net/projects/mingw-w64/files/) (scroll down and select x86_64-posix-seh, dont use the autoinstaller it doesnt work)
3. Search for "advanced system settings" and open the system properties dialog.
1. Click the `Environment Variables` button
2. Under system variables find the `Path`. Edit and add `C:\MinGW\bin` (replace with the correct path to where you extracted MingW64).
NOTE: The `make` command in Windows will be `mingw32-make` with MingW. For example `make pre-ui` will be `mingw32-make pre-ui`
### macOS
1. If you don't have it already, install the [Homebrew package manager](https://brew.sh).
2. Install dependencies: `brew install go git yarn gcc make`
## Commands
* `make pre-ui` - Installs the UI dependencies. Only needs to be run once before building the UI for the first time, or if the dependencies are updated
* `make generate` - Generate Go and UI GraphQL files
* `make fmt-ui` - Formats the UI source code
* `make ui` - Builds the frontend
* `make build` - Builds the binary (make sure to build the UI as well... see below)
* `make docker-build` - Locally builds and tags a complete 'stash/build' docker image
* `make lint` - Run the linter on the backend
* `make fmt` - Run `go fmt`
* `make it` - Run the unit and integration tests
* `make validate` - Run all of the tests and checks required to submit a PR
* `make ui-start` - Runs the UI in development mode. Requires a running stash server to connect to. Stash server port can be changed from the default of `9999` using environment variable `VITE_APP_PLATFORM_PORT`. UI runs on port `3000` or the next available port.
## Building a release
1. Run `make generate` to create generated files
2. Run `make ui` to compile the frontend
3. Run `make build` to build the executable for your current platform
## Cross compiling
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
where the app can be cross-compiled. This process is kicked off by CI via the `scripts/cross-compile.sh` script. Run the following
command to open a bash shell to the container to poke around:
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashappdev/compiler:latest /bin/bash`
## Profiling
Stash can be profiled using the `--cpuprofile <output profile filename>` command line flag.
The resulting file can then be used with pprof as follows:
`go tool pprof <path to binary> <path to profile filename>`
With `graphviz` installed and in the path, a call graph can be generated with:
`go tool pprof -svg <path to binary> <path to profile filename> > <output svg file>`

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="185px" viewBox="0 0 256 185" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M250.715745,70.4971666 C244.951102,66.4973277 231.740464,64.997388 221.412146,66.9973071 C220.211179,56.9977092 214.68673,48.2480609 205.078993,40.4983724 L199.554544,36.4985331 L195.711449,42.248302 C190.90758,49.7480006 188.505646,60.2475786 189.226226,70.2471769 C189.46642,73.7470364 190.667387,79.9967847 194.270289,85.496564 C190.90758,87.4964838 183.941971,89.996383 174.814621,89.996383 L1.15476998,89.996383 L0.674383104,91.9963028 C-1.00697093,101.9959 -1.00697093,133.244645 18.6888904,157.243681 C33.5808831,175.492947 55.6786788,184.742575 84.7420842,184.742575 C147.672763,184.742575 194.270289,154.493791 216.127891,99.7459909 C224.774854,99.9959813 243.269748,99.7459909 252.637292,80.996745 C252.877486,80.4967649 253.357872,79.4968046 255.039227,75.7469554 L256,73.7470364 L250.715745,70.4971666 L250.715745,70.4971666 Z M139.986573,0 L113.565295,0 L113.565295,24.9989952 L139.986573,24.9989952 L139.986573,0 L139.986573,0 Z M139.986573,29.9987943 L113.565295,29.9987943 L113.565295,54.9977896 L139.986573,54.9977896 L139.986573,29.9987943 L139.986573,29.9987943 Z M108.761427,29.9987943 L82.3401495,29.9987943 L82.3401495,54.9977896 L108.761427,54.9977896 L108.761427,29.9987943 L108.761427,29.9987943 Z M77.5362814,29.9987943 L51.1150037,29.9987943 L51.1150037,54.9977896 L77.5362814,54.9977896 L77.5362814,29.9987943 L77.5362814,29.9987943 Z M46.311135,59.9975886 L19.8898576,59.9975886 L19.8898576,84.9965839 L46.311135,84.9965839 L46.311135,59.9975886 L46.311135,59.9975886 Z M77.5362814,59.9975886 L51.1150037,59.9975886 L51.1150037,84.9965839 L77.5362814,84.9965839 L77.5362814,59.9975886 L77.5362814,59.9975886 Z M108.761427,59.9975886 L82.3401495,59.9975886 L82.3401495,84.9965839 L108.761427,84.9965839 L108.761427,59.9975886 L108.761427,59.9975886 Z M139.986573,59.9975886 L113.565295,59.9975886 L113.565295,84.9965839 L139.986573,84.9965839 L139.986573,59.9975886 L139.986573,59.9975886 Z M171.211719,59.9975886 L144.790441,59.9975886 L144.790441,84.9965839 L171.211719,84.9965839 L171.211719,59.9975886 L171.211719,59.9975886 Z" fill="#2396ED" fill-rule="nonzero"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="295px" viewBox="0 0 256 295" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="6.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
</filter>
<linearGradient x1="48.5477412%" y1="115.276174%" x2="51.0473804%" y2="41.3637237%" id="linearGradient-2">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="54.4065463%" y1="2.40410545%" x2="46.1753957%" y2="90.5422349%" id="linearGradient-3">
<stop stop-color="#FFFFFF" stop-opacity="0.8" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="51.859653%" y1="88.2477484%" x2="47.9469396%" y2="9.74782136%" id="linearGradient-4">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.9251097%" y1="85.4900173%" x2="49.9236843%" y2="13.8109272%" id="linearGradient-5">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="53.9014071%" y1="3.10177585%" x2="45.9555354%" y2="93.8949571%" id="linearGradient-6">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="45.5928761%" y1="5.47459052%" x2="54.811359%" y2="93.5235162%" id="linearGradient-7">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.9844987%" y1="89.8452442%" x2="49.9844987%" y2="40.6316864%" id="linearGradient-8">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="53.5047131%" y1="99.97524%" x2="42.7455968%" y2="23.5451715%" id="linearGradient-9">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.8413363%" y1="13.2289558%" x2="50.2412612%" y2="94.6729694%" id="linearGradient-10">
<stop stop-color="#FFFFFF" stop-opacity="0.8" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.9272298%" y1="37.3270337%" x2="50.7270446%" y2="92.7824735%" id="linearGradient-11">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.8755597%" y1="2.29900584%" x2="49.8755597%" y2="81.203617%" id="linearGradient-12">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.8334391%" y1="2.27189065%" x2="49.8240398%" y2="71.7989617%" id="linearGradient-13">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="53.4670683%" y1="48.9213861%" x2="38.9488708%" y2="98.0999776%" id="linearGradient-14">
<stop stop-color="#FFA63F" offset="0%"></stop>
<stop stop-color="#FFFF00" offset="100%"></stop>
</linearGradient>
<linearGradient x1="52.3731508%" y1="143.008909%" x2="47.57909%" y2="-64.6215389%" id="linearGradient-15">
<stop stop-color="#FFEED7" offset="0%"></stop>
<stop stop-color="#BDBFC2" offset="100%"></stop>
</linearGradient>
<linearGradient x1="30.580815%" y1="34.0241079%" x2="65.8867024%" y2="89.175349%" id="linearGradient-16">
<stop stop-color="#FFA63F" offset="0%"></stop>
<stop stop-color="#FFFF00" offset="100%"></stop>
</linearGradient>
<linearGradient x1="59.5715091%" y1="-17.2155207%" x2="48.3608522%" y2="66.1184465%" id="linearGradient-17">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="47.7689553%" y1="1.56481301%" x2="51.3733028%" y2="104.312856%" id="linearGradient-18">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="43.5495626%" y1="4.5334861%" x2="57.1143288%" y2="92.8267174%" id="linearGradient-19">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
<linearGradient x1="49.7328042%" y1="17.6085216%" x2="50.5582487%" y2="99.3854667%" id="linearGradient-20">
<stop stop-color="#FFA63F" offset="0%"></stop>
<stop stop-color="#FFFF00" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50.1697217%" y1="2.89048531%" x2="49.6802359%" y2="94.1704279%" id="linearGradient-21">
<stop stop-color="#FFFFFF" stop-opacity="0.65" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g fill="none">
<g transform="translate(10.000000, 0.000000)">
<path d="M235.125423,249.358628 C235.125423,266.714271 182.507524,280.855905 117.584567,280.855905 C52.6616093,280.855905 0.0437105058,266.806099 0.0437105058,249.358628 L0.0437105058,249.358628 C0.0437105058,232.002986 52.6616093,217.861352 117.584567,217.861352 C182.507524,217.861352 235.033594,232.002986 235.125423,249.358628 L235.125423,249.358628 L235.125423,249.358628 Z" fill="#000" fill-opacity="0.2" filter="url(#filter-1)"></path>
<path d="M53.2125821,215.473804 C41.8258117,199.128278 39.6219206,145.867578 66.160442,113.084699 C79.2919595,97.3819748 82.6896249,86.4543483 83.6997416,71.6699125 C84.434372,54.8652433 71.8538272,4.81855066 119.237485,1.05357012 C167.263944,-2.80323922 164.600909,44.5804184 164.325423,69.6496791 C164.141765,90.7703016 179.844489,102.799874 190.680286,119.329056 C210.607135,149.632558 208.954216,201.791313 186.915306,230.074582 C158.999353,265.428667 135.123866,250.093259 119.237485,251.378862 C89.4849556,253.123609 88.4748389,268.918162 53.2125821,215.473804 L53.2125821,215.473804 Z" fill="#000000"></path>
<path d="M169.10052,122.451235 C177.365111,130.073025 198.76122,164.141508 164.876395,185.445788 C152.938652,192.88392 175.528535,221.167189 186.364333,207.484699 C205.556551,182.874582 193.343321,143.571858 181.772893,129.522053 C174.059275,119.604543 162.121532,115.747734 169.10052,122.451235 L169.10052,122.451235 Z" fill="url(#linearGradient-2)"></path>
<path d="M166.8048,117.859796 C180.395461,128.879251 205.097407,167.447344 169.008691,192.608434 C157.162777,200.413881 179.477174,225.115827 192.057718,212.535282 C235.676395,168.641119 190.955773,118.227111 175.528535,100.871469 C161.754216,85.719718 149.540987,104.360963 166.8048,117.859796 L166.8048,117.859796 Z" stroke="#000000" stroke-width="0.9773" fill="#000000"></path>
<path d="M147.245267,25.0208853 C146.786123,37.60143 132.919975,48.5290565 116.298963,49.5391732 C99.6779518,50.54929 86.638263,40.9990954 87.097407,28.4185507 L87.097407,28.4185507 C87.556551,15.8380059 101.422699,4.91037946 118.043711,3.90026272 C134.664722,2.98197479 147.704411,12.4403405 147.245267,25.0208853 L147.245267,25.0208853 L147.245267,25.0208853 Z" fill="url(#linearGradient-3)"></path>
<path d="M107.483399,54.9570721 C107.942543,63.1298347 104.085734,70.0169942 98.7596638,70.2924806 C93.4335938,70.567967 88.7503253,64.2317802 88.2911813,56.0590176 L88.2911813,56.0590176 C87.8320374,47.8862549 91.6888467,40.9990954 97.0149167,40.723609 C102.340987,40.4481226 107.024255,46.7843094 107.483399,54.9570721 L107.483399,54.9570721 L107.483399,54.9570721 Z" fill="url(#linearGradient-4)"></path>
<path d="M117.125423,55.5998736 C117.30908,65.0582394 123.461609,72.5882005 130.807913,72.4045429 C138.154216,72.2208853 143.93943,64.4154378 143.755773,54.8652433 L143.755773,54.8652433 C143.572115,45.4068775 137.419586,37.8769164 130.073282,38.060574 C122.726979,38.2442316 116.849936,46.1415079 117.125423,55.5998736 L117.125423,55.5998736 L117.125423,55.5998736 Z" fill="url(#linearGradient-5)"></path>
<path d="M123.186123,57.7119359 C123.094294,62.9461771 125.6655,67.1703016 129.063166,67.1703016 C132.369002,67.1703016 135.215695,62.9461771 135.307524,57.8037647 L135.307524,57.8037647 C135.399353,52.5695234 132.828146,48.3453989 129.430481,48.3453989 C126.032816,48.3453989 123.277952,52.5695234 123.186123,57.7119359 L123.186123,57.7119359 L123.186123,57.7119359 Z" fill="#000000"></path>
<path d="M101.973672,57.8037647 C102.432816,62.119718 100.779897,65.7928697 98.3923486,66.1601849 C96.0048,66.4356713 93.7090802,63.2216635 93.2499362,58.9057102 L93.2499362,58.9057102 C92.7907922,54.5897569 94.4437105,50.9166051 96.8312591,50.54929 C99.2188078,50.2738036 101.514528,53.4878114 101.973672,57.8037647 L101.973672,57.8037647 L101.973672,57.8037647 Z" fill="#000000"></path>
<path d="M124.563555,54.7734145 C124.288068,57.7119359 125.6655,60.0994845 127.593905,60.2831421 C129.52231,60.4667997 131.358886,58.1710798 131.634372,55.3243872 L131.634372,55.3243872 C131.909858,52.3858658 130.532426,49.9983172 128.604022,49.8146596 C126.675617,49.631002 124.839041,51.9267219 124.563555,54.7734145 L124.563555,54.7734145 L124.563555,54.7734145 Z" fill="url(#linearGradient-6)"></path>
<path d="M99.9534381,55.5080448 C100.228925,57.8955935 99.2188078,60.0076557 97.7495471,60.1913133 C96.2802864,60.3749709 94.9028545,58.538395 94.6273681,56.0590176 L94.6273681,56.0590176 C94.3518817,53.6714689 95.3619984,51.5594067 96.8312591,51.3757491 C98.3005198,51.1920915 99.6779518,53.1204962 99.9534381,55.5080448 L99.9534381,55.5080448 L99.9534381,55.5080448 Z" fill="url(#linearGradient-7)"></path>
<path d="M71.0273681,145.68392 C77.5472125,130.899485 91.4133603,104.911936 91.6888467,84.80143 C91.6888467,68.8232199 139.531648,64.9664106 143.388458,80.9446207 C147.245267,96.9228308 156.979119,120.798317 163.223477,132.368745 C169.467835,143.847344 187.558107,180.487033 168.274061,212.443453 C150.918419,240.726722 98.3005198,263.132948 70.2009089,208.586644 C60.6507144,189.669913 62.3954615,166.25357 71.0273681,145.68392 L71.0273681,145.68392 Z" fill="url(#linearGradient-8)"></path>
<path d="M65.1503253,134.664465 C59.5487689,145.224776 47.9783409,172.957072 76.2616093,188.108823 C106.65694,204.270691 106.565111,237.420885 70.0172514,221.626333 C36.5915704,207.39287 51.3760062,149.724387 60.7425432,135.950068 C66.8032436,126.308045 75.986123,114.46213 65.1503253,134.664465 L65.1503253,134.664465 Z" fill="url(#linearGradient-9)"></path>
<path d="M69.9254226,122.726722 C61.0180296,137.235671 39.7137494,171.395983 68.2725043,189.210769 C106.65694,212.810769 95.8211424,236.31894 60.7425432,215.106488 C11.3386521,185.537617 54.7736716,125.848901 74.5168623,103.07536 C97.1067455,77.5469553 78.8328156,107.758628 69.9254226,122.726722 L69.9254226,122.726722 Z" stroke="#000000" stroke-width="1.25" fill="#000000"></path>
<path d="M156.428146,151.285477 C156.428146,167.447344 140.90908,188.384309 114.27873,188.200652 C86.8219206,188.384309 75.1596638,167.447344 75.1596638,151.285477 C75.1596638,135.123609 93.341765,121.992092 115.747991,121.992092 C138.246045,122.08392 156.428146,135.123609 156.428146,151.285477 L156.428146,151.285477 Z" fill="url(#linearGradient-10)"></path>
<path d="M141.919197,100.504154 C141.643711,117.216994 130.716084,121.165632 116.941765,121.165632 C103.167446,121.165632 93.1581074,118.686255 91.9643331,100.504154 C91.9643331,89.1173833 103.167446,82.5057102 116.941765,82.5057102 C130.716084,82.4138814 141.919197,89.0255546 141.919197,100.504154 L141.919197,100.504154 Z" fill="url(#linearGradient-11)"></path>
<path d="M58.6304809,126.216216 C67.6297027,112.533726 86.638263,91.504932 62.2118039,129.154737 C42.3767844,160.19287 54.8655004,180.119718 61.293516,185.629446 C79.8429323,202.158628 79.1083019,213.269913 64.5075237,204.546177 C33.1939051,185.904932 39.7137494,154.499485 58.6304809,126.216216 L58.6304809,126.216216 Z" fill="url(#linearGradient-12)"></path>
<path d="M188.935539,131.817772 C181.130092,115.747734 156.336318,74.9757491 190.129314,122.359407 C220.89196,165.243453 199.312193,195.087811 195.455384,198.026333 C191.598574,200.964854 178.650714,206.933726 182.415695,196.557072 C186.272504,186.180418 205.372893,166.529056 188.935539,131.817772 L188.935539,131.817772 Z" fill="url(#linearGradient-13)"></path>
<path d="M51.8351502,258.541508 C31.2655004,247.613881 1.42114241,260.65357 12.2569401,231.084699 C14.4608311,224.381197 9.0429323,214.280029 12.5324265,207.760185 C16.6647222,199.77108 25.5721152,201.515827 30.8981852,196.189757 C36.1324265,190.680029 39.438263,181.129835 49.263944,182.599095 C58.9977961,184.068356 65.5176405,196.006099 72.3129712,210.698706 C77.3635549,221.167189 95.1783409,235.951625 93.9845665,247.70571 C92.5153058,265.704154 72.0374848,269.101819 51.8351502,258.541508 L51.8351502,258.541508 Z" stroke="#E68C3F" stroke-width="6.25" fill="url(#linearGradient-14)"></path>
<path d="M201.607913,189.11894 C198.485734,194.995983 185.446045,204.454348 176.72231,201.974971 C167.906746,199.587422 163.866279,186.180418 165.611026,175.987422 C167.263944,164.600652 176.72231,163.95785 188.660053,169.651235 C201.516084,175.987422 205.372893,181.313492 201.607913,189.11894 L201.607913,189.11894 Z" fill="url(#linearGradient-15)"></path>
<path d="M194.445267,253.490924 C209.505189,235.216994 243.022699,238.981975 220.432816,213.912714 C215.657718,208.494815 217.126979,196.924387 211.249936,191.965632 C204.362777,185.904932 196.740987,190.863687 189.761998,187.741508 C182.78301,184.343842 175.436707,177.823998 166.896629,182.415438 C158.356551,187.098706 157.438263,199.220107 156.611804,215.198317 C155.877174,226.676916 145.408691,245.869134 151.010247,256.429446 C159.091181,272.774971 180.119975,270.57108 194.445267,253.490924 L194.445267,253.490924 Z" stroke="#E68C3F" stroke-width="6.2507" fill="url(#linearGradient-16)"></path>
<path d="M187.925423,229.064465 C211.249936,194.628667 193.894294,194.904154 188.017251,192.241119 C182.140209,189.486255 175.987679,184.068356 169.10052,187.833337 C162.21336,191.690146 161.846045,201.607656 161.662388,214.647344 C161.386901,224.013881 153.581454,239.716605 158.264722,248.440341 C163.958107,258.633337 177.732426,243.848901 187.925423,229.064465 L187.925423,229.064465 Z" fill="url(#linearGradient-17)"></path>
<path d="M47.0600529,234.02322 C12.1651113,211.433337 28.5106366,203.719718 33.7448778,200.138395 C40.0810646,195.546955 40.1728934,186.731391 47.9783409,187.55785 C55.7837883,188.384309 60.375228,198.026333 65.6094693,209.964076 C69.4662786,218.504154 82.8732825,229.890924 81.8631658,239.716605 C80.5775626,251.287033 62.1199751,243.665243 47.0600529,234.02322 L47.0600529,234.02322 Z" fill="url(#linearGradient-18)"></path>
<path d="M199.587679,188.843453 C196.832816,193.618551 185.629703,201.148512 178.19157,199.128278 C170.569781,197.199874 167.080286,186.455905 168.641376,178.374971 C170.018808,169.192092 178.19157,168.732948 188.476395,173.324387 C199.404022,178.283142 202.801687,182.507267 199.587679,188.843453 L199.587679,188.843453 Z" fill="#000000"></path>
<path d="M192.057718,186.180418 C190.312971,189.486255 182.966668,194.720496 177.824255,193.343064 C172.681843,191.965632 170.110637,184.5275 170.937096,178.925944 C171.671726,172.589757 177.181454,172.222442 184.160442,175.344621 C191.690403,178.834115 194.077952,181.772636 192.057718,186.180418 L192.057718,186.180418 Z" fill="url(#linearGradient-19)"></path>
<path d="M97.1067455,66.3438425 C100.779897,62.9461771 109.68729,52.5695234 126.583788,63.4053211 C129.705967,65.4255546 132.277174,65.6092121 138.246045,68.1804184 C150.275617,73.1391732 144.582232,85.0769164 131.726201,89.1173833 C126.216473,90.8621304 121.257718,97.5656324 111.340209,96.9228308 C102.800131,96.4636868 100.59624,90.8621304 95.3619984,87.8317802 C86.0872903,82.597539 84.7098584,75.5267219 89.760442,71.7617413 C94.8110257,67.9967608 96.7394304,66.6193289 97.1067455,66.3438425 L97.1067455,66.3438425 Z" stroke="#E68C3F" stroke-width="3.75" fill="url(#linearGradient-20)"></path>
<path d="M138.429703,75.9858658 C133.379119,76.2613522 122.451493,87.1889787 110.972893,87.1889787 C99.4942942,87.1889787 92.6071346,76.5368386 90.8623875,76.5368386" stroke="#E68C3F" stroke-width="2.5"></path>
<path d="M102.800131,65.4255546 C104.636707,63.7726363 110.421921,59.2730254 118.043711,63.8644651 C119.696629,64.782753 121.349547,65.7928697 123.737096,67.1703016 C128.604022,70.0169942 126.216473,74.14929 120.33943,76.7204962 C117.676395,77.8224417 113.268613,80.2099904 109.962777,80.0263328 C106.289625,79.6590176 103.810247,77.2714689 101.422699,75.7103795 C96.9230879,72.7718581 97.1985743,70.2924806 99.3106366,68.364076 C100.871726,66.8948153 102.616473,65.5173833 102.800131,65.4255546 L102.800131,65.4255546 Z" fill="url(#linearGradient-21)"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 42 42" xmlns="http://www.w3.org/2000/svg"><path d="m23.091 14.018v-0.342l-1.063 0.073c-0.301 0.019-0.527 0.083-0.679 0.191-0.152 0.109-0.228 0.26-0.228 0.453 0 0.188 0.075 0.338 0.226 0.449 0.15 0.112 0.352 0.167 0.604 0.167 0.161 0 0.312-0.025 0.451-0.074s0.261-0.118 0.363-0.206c0.102-0.087 0.182-0.191 0.239-0.312 0.058-0.121 0.087-0.254 0.087-0.399zm-2.091-13.768c-11.579 0-20.75 9.171-20.75 20.75 0 11.58 9.171 20.75 20.75 20.75s20.75-9.17 20.75-20.75c0-11.579-9.17-20.75-20.75-20.75zm4.028 12.299c0.098-0.275 0.236-0.511 0.415-0.707s0.394-0.347 0.646-0.453 0.533-0.159 0.842-0.159c0.279 0 0.531 0.042 0.755 0.125 0.225 0.083 0.417 0.195 0.578 0.336s0.289 0.305 0.383 0.493 0.15 0.387 0.169 0.596h-0.833c-0.021-0.115-0.059-0.223-0.113-0.322s-0.125-0.185-0.213-0.258c-0.089-0.073-0.193-0.13-0.312-0.171-0.12-0.042-0.254-0.062-0.405-0.062-0.177 0-0.338 0.036-0.481 0.107-0.144 0.071-0.267 0.172-0.369 0.302s-0.181 0.289-0.237 0.475c-0.057 0.187-0.085 0.394-0.085 0.622 0 0.236 0.028 0.448 0.085 0.634 0.056 0.187 0.136 0.344 0.24 0.473 0.103 0.129 0.228 0.228 0.373 0.296s0.305 0.103 0.479 0.103c0.285 0 0.517-0.067 0.697-0.201s0.296-0.33 0.35-0.588h0.834c-0.024 0.228-0.087 0.436-0.189 0.624s-0.234 0.348-0.396 0.481c-0.163 0.133-0.354 0.236-0.574 0.308s-0.462 0.109-0.725 0.109c-0.312 0-0.593-0.052-0.846-0.155-0.252-0.103-0.469-0.252-0.649-0.445s-0.319-0.428-0.417-0.705-0.147-0.588-0.147-0.935c-2e-3 -0.339 0.047-0.647 0.145-0.923zm-11.853-1.262h0.834v0.741h0.016c0.051-0.123 0.118-0.234 0.2-0.33 0.082-0.097 0.176-0.179 0.284-0.248 0.107-0.069 0.226-0.121 0.354-0.157 0.129-0.036 0.265-0.054 0.407-0.054 0.306 0 0.565 0.073 0.775 0.219 0.211 0.146 0.361 0.356 0.449 0.63h0.021c0.056-0.132 0.13-0.25 0.221-0.354s0.196-0.194 0.314-0.268 0.248-0.13 0.389-0.169 0.289-0.058 0.445-0.058c0.215 0 0.41 0.034 0.586 0.103s0.326 0.165 0.451 0.29 0.221 0.277 0.288 0.455 0.101 0.376 0.101 0.594v2.981h-0.87v-2.772c0-0.287-0.074-0.51-0.222-0.667-0.147-0.157-0.358-0.236-0.632-0.236-0.134 0-0.257 0.024-0.369 0.071-0.111 0.047-0.208 0.113-0.288 0.198-0.081 0.084-0.144 0.186-0.189 0.304-0.046 0.118-0.069 0.247-0.069 0.387v2.715h-0.858v-2.844c0-0.126-0.02-0.24-0.059-0.342s-0.094-0.189-0.167-0.262c-0.072-0.073-0.161-0.128-0.264-0.167-0.104-0.039-0.22-0.059-0.349-0.059-0.134 0-0.258 0.025-0.373 0.075-0.114 0.05-0.212 0.119-0.294 0.207-0.082 0.089-0.146 0.193-0.191 0.314-0.044 0.12-0.116 0.252-0.116 0.394v2.683h-0.825v-4.374zm1.893 20.939c-3.825 0-6.224-2.658-6.224-6.9s2.399-6.909 6.224-6.909 6.215 2.667 6.215 6.909c0 4.241-2.39 6.9-6.215 6.9zm7.082-16.575c-0.141 0.036-0.285 0.054-0.433 0.054-0.218 0-0.417-0.031-0.598-0.093-0.182-0.062-0.337-0.149-0.467-0.262s-0.232-0.249-0.304-0.409c-0.073-0.16-0.109-0.338-0.109-0.534 0-0.384 0.143-0.684 0.429-0.9s0.7-0.342 1.243-0.377l1.18-0.068v-0.338c0-0.252-0.08-0.445-0.24-0.576s-0.386-0.197-0.679-0.197c-0.118 0-0.229 0.015-0.331 0.044-0.102 0.03-0.192 0.072-0.27 0.127s-0.143 0.121-0.193 0.198c-0.051 0.076-0.086 0.162-0.105 0.256h-0.818c5e-3 -0.193 0.053-0.372 0.143-0.536s0.212-0.306 0.367-0.427 0.336-0.215 0.546-0.282 0.438-0.101 0.685-0.101c0.266 0 0.507 0.033 0.723 0.101s0.401 0.163 0.554 0.288 0.271 0.275 0.354 0.451 0.125 0.373 0.125 0.59v3.001h-0.833v-0.729h-0.021c-0.062 0.118-0.14 0.225-0.235 0.32-0.096 0.095-0.203 0.177-0.322 0.244-0.12 0.067-0.25 0.119-0.391 0.155zm5.503 16.575c-2.917 0-4.9-1.528-5.038-3.927h1.899c0.148 1.371 1.473 2.279 3.288 2.279 1.741 0 2.992-0.908 2.992-2.149 0-1.074-0.76-1.723-2.519-2.167l-1.714-0.426c-2.464-0.611-3.584-1.732-3.584-3.575 0-2.269 1.982-3.844 4.807-3.844 2.76 0 4.686 1.584 4.76 3.862h-1.88c-0.13-1.371-1.25-2.214-2.918-2.214-1.658 0-2.806 0.852-2.806 2.084 0 0.972 0.722 1.547 2.482 1.991l1.445 0.361c2.751 0.667 3.881 1.751 3.881 3.696-1e-3 2.482-1.964 4.029-5.095 4.029zm-12.585-12.106c-2.621 0-4.26 2.01-4.26 5.205 0 3.186 1.639 5.196 4.26 5.196 2.612 0 4.26-2.01 4.26-5.196 1e-3 -3.195-1.648-5.205-4.26-5.205z"/></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="257px" viewBox="0 0 256 257" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M0,36.3573818 L104.619084,22.1093454 L104.664817,123.02292 L0.0955693151,123.618411 L0,36.3573818 Z M104.569248,134.650129 L104.650452,235.651651 L0.0812046021,221.274919 L0.0753414539,133.972642 L104.569248,134.650129 Z M117.25153,20.2454506 L255.967753,6.21724894e-15 L255.967753,121.739477 L117.25153,122.840723 L117.25153,20.2454506 Z M256,135.599959 L255.96746,256.791232 L117.251237,237.213007 L117.056874,135.373055 L256,135.599959 Z" fill="#00ADEF"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 734 B

96
go.mod
View File

@@ -1,10 +1,10 @@
module github.com/stashapp/stash
require (
github.com/99designs/gqlgen v0.12.2
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953
github.com/99designs/gqlgen v0.17.2
github.com/Yamashou/gqlgenc v0.0.6
github.com/anacrolix/dms v1.2.2
github.com/antchfx/htmlquery v1.2.3
github.com/antchfx/htmlquery v1.2.5-0.20211125074323-810ee8082758
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84
github.com/chromedp/chromedp v0.7.3
github.com/corona10/goimagehash v1.0.3
@@ -16,80 +16,92 @@ require (
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/h2non/filetype v1.0.8
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jmoiron/sqlx v1.3.1
github.com/json-iterator/go v1.1.9
github.com/json-iterator/go v1.1.12
github.com/mattn/go-sqlite3 v1.14.6
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/remeh/sizedwaitgroup v1.0.0
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac
github.com/rs/cors v1.6.0
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
github.com/sirupsen/logrus v1.8.1
github.com/spf13/afero v1.2.0 // indirect
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1
github.com/tidwall/gjson v1.8.1
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.9.3
github.com/tidwall/pretty v1.2.0 // indirect
github.com/vektah/gqlparser/v2 v2.0.1
github.com/vektra/mockery/v2 v2.2.1
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
github.com/vektra/mockery/v2 v2.10.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/tools v0.1.0 // indirect
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9
golang.org/x/sys v0.0.0-20220329152356-43be30ef3008
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.org/x/tools v0.1.10 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/agnivade/levenshtein v1.1.0 // indirect
github.com/antchfx/xpath v1.1.6 // indirect
github.com/asticode/go-astisub v0.20.0
github.com/go-chi/httplog v0.2.1
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/hashicorp/golang-lru v0.5.4
github.com/kermieisinthehouse/gosx-notifier v0.1.1
github.com/kermieisinthehouse/systray v1.2.4
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/spf13/cast v1.4.1
github.com/vearutop/statigz v1.1.6
github.com/vektah/gqlparser/v2 v2.4.1
)
require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/antchfx/xpath v1.2.0 // indirect
github.com/asticode/go-astikit v0.20.0 // indirect
github.com/asticode/go-astits v1.8.0 // indirect
github.com/chromedp/sysutil v1.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.4.7 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi/v5 v5.0.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0-rc.5 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 // indirect
github.com/matryer/moq v0.2.6 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.18.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/spf13/cast v1.3.0 // indirect
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/jwalterweatherman v1.0.0 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/tidwall/match v1.0.3 // indirect
github.com/urfave/cli/v2 v2.1.1 // indirect
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e // indirect
go.uber.org/atomic v1.6.0 // indirect
golang.org/x/mod v0.4.1 // indirect
golang.org/x/text v0.3.6 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/urfave/cli/v2 v2.4.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/ini.v1 v1.51.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999

500
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,16 @@ exec:
model:
filename: pkg/models/generated_models.go
resolver:
filename: pkg/api/resolver.go
filename: internal/api/resolver.go
type: Resolver
struct_tag: gqlgen
models:
# Scalars
Timestamp:
model: github.com/stashapp/stash/pkg/models.Timestamp
# Objects
Gallery:
model: github.com/stashapp/stash/pkg/models.Gallery
Image:
@@ -40,3 +44,6 @@ models:
model: github.com/stashapp/stash/pkg/models.SavedFilter
StashID:
model: github.com/stashapp/stash/pkg/models.StashID
SceneCaption:
model: github.com/stashapp/stash/pkg/models.SceneCaption

View File

@@ -7,6 +7,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
databasePath
generatedPath
metadataPath
scrapersPath
cachePath
calculateMD5
videoFileNamingAlgorithm
@@ -24,7 +25,6 @@ fragment ConfigGeneralData on ConfigGeneralResult {
username
password
maxSessionAge
trustedProxies
logFile
logOut
logLevel
@@ -44,6 +44,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
endpoint
api_key
}
pythonPath
}
fragment ConfigInterfaceData on ConfigInterfaceResult {
@@ -51,13 +52,30 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
soundOnPreview
wallShowTitle
wallPlayback
showScrubber
maximumLoopDuration
noBrowser
notificationsEnabled
autostartVideo
autostartVideoOnPlaySelected
continuePlaylistDefault
showStudioAsText
css
cssEnabled
language
slideshowDelay
imageLightbox {
slideshowDelay
displayMode
scaleUp
resetZoomOnNav
scrollMode
scrollAttemptsBeforeChange
}
disableDropdownCreate {
performer
tag
studio
}
handyKey
funscriptOffset
}
@@ -76,6 +94,81 @@ fragment ConfigScrapingData on ConfigScrapingResult {
excludeTagPatterns
}
fragment IdentifyFieldOptionsData on IdentifyFieldOptions {
field
strategy
createMissing
}
fragment IdentifyMetadataOptionsData on IdentifyMetadataOptions {
fieldOptions {
...IdentifyFieldOptionsData
}
setCoverImage
setOrganized
includeMalePerformers
}
fragment ScraperSourceData on ScraperSource {
stash_box_index
stash_box_endpoint
scraper_id
}
fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult {
scan {
useFileMetadata
stripFileExtension
scanGeneratePreviews
scanGenerateImagePreviews
scanGenerateSprites
scanGeneratePhashes
scanGenerateThumbnails
}
identify {
sources {
source {
...ScraperSourceData
}
options {
...IdentifyMetadataOptionsData
}
}
options {
...IdentifyMetadataOptionsData
}
}
autoTag {
performers
studios
tags
}
generate {
sprites
previews
imagePreviews
previewOptions {
previewSegments
previewSegmentDuration
previewExcludeStart
previewExcludeEnd
previewPreset
}
markers
markerImagePreviews
markerScreenshots
transcodes
phashes
interactiveHeatmapsSpeeds
}
deleteFile
deleteGenerated
}
fragment ConfigData on ConfigResult {
general {
...ConfigGeneralData
@@ -89,4 +182,8 @@ fragment ConfigData on ConfigResult {
scraping {
...ConfigScrapingData
}
defaults {
...ConfigDefaultSettingsData
}
ui
}

View File

@@ -2,6 +2,8 @@ fragment GalleryData on Gallery {
id
checksum
path
created_at
updated_at
title
date
url

View File

@@ -6,6 +6,8 @@ fragment ImageData on Image {
organized
o_counter
path
created_at
updated_at
file {
size

View File

@@ -2,8 +2,22 @@ fragment SlimPerformerData on Performer {
id
name
gender
url
twitter
instagram
image_path
favorite
ignore_auto_tag
country
birthdate
ethnicity
hair_color
eye_color
height
fake_tits
career_length
tattoos
piercings
tags {
id
name
@@ -13,4 +27,6 @@ fragment SlimPerformerData on Performer {
stash_id
}
rating
death_date
weight
}

View File

@@ -18,6 +18,7 @@ fragment PerformerData on Performer {
piercings
aliases
favorite
ignore_auto_tag
image_path
scene_count
image_count

View File

@@ -12,6 +12,11 @@ fragment SlimSceneData on Scene {
path
phash
interactive
interactive_speed
captions {
language_code
caption_type
}
file {
size
@@ -33,6 +38,8 @@ fragment SlimSceneData on Scene {
chapters_vtt
sprite
funscript
interactive_heatmap
caption
}
scene_markers {

View File

@@ -12,6 +12,13 @@ fragment SceneData on Scene {
path
phash
interactive
interactive_speed
captions {
language_code
caption_type
}
created_at
updated_at
file {
size
@@ -33,6 +40,8 @@ fragment SceneData on Scene {
chapters_vtt
sprite
funscript
interactive_heatmap
caption
}
scene_markers {
@@ -66,4 +75,10 @@ fragment SceneData on Scene {
endpoint
stash_id
}
sceneStreams {
url
mime_type
label
}
}

View File

@@ -14,6 +14,7 @@ fragment StudioData on Studio {
name
image_path
}
ignore_auto_tag
image_path
scene_count
image_count

View File

@@ -2,6 +2,7 @@ fragment TagData on Tag {
id
name
aliases
ignore_auto_tag
image_path
scene_count
scene_marker_count

View File

@@ -30,6 +30,16 @@ mutation ConfigureScraping($input: ConfigScrapingInput!) {
}
}
mutation ConfigureDefaults($input: ConfigDefaultSettingsInput!) {
configureDefaults(input: $input) {
...ConfigDefaultSettingsData
}
}
mutation ConfigureUI($input: Map!) {
configureUI(input: $input)
}
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
generateAPIKey(input: $input)
}

View File

@@ -26,6 +26,10 @@ mutation MetadataAutoTag($input: AutoTagMetadataInput!) {
metadataAutoTag(input: $input)
}
mutation MetadataIdentify($input: IdentifyMetadataInput!) {
metadataIdentify(input: $input)
}
mutation MetadataClean($input: CleanMetadataInput!) {
metadataClean(input: $input)
}

View File

@@ -22,6 +22,12 @@ mutation MovieUpdate($input: MovieUpdateInput!) {
}
}
mutation BulkMovieUpdate($input: BulkMovieUpdateInput!) {
bulkMovieUpdate(input: $input) {
...MovieData
}
}
mutation MovieDestroy($id: ID!) {
movieDestroy(input: { id: $id })
}

View File

@@ -5,3 +5,11 @@ mutation SubmitStashBoxFingerprints($input: StashBoxFingerprintSubmissionInput!)
mutation StashBoxBatchPerformerTag($input: StashBoxBatchPerformerTagInput!) {
stashBoxBatchPerformerTag(input: $input)
}
mutation SubmitStashBoxSceneDraft($input: StashBoxDraftSubmissionInput!) {
submitStashBoxSceneDraft(input: $input)
}
mutation SubmitStashBoxPerformerDraft($input: StashBoxDraftSubmissionInput!) {
submitStashBoxPerformerDraft(input: $input)
}

View File

@@ -1,4 +1,10 @@
query FindSavedFilters($mode: FilterMode!) {
query FindSavedFilter($id: ID!) {
findSavedFilter(id: $id) {
...SavedFilterData
}
}
query FindSavedFilters($mode: FilterMode) {
findSavedFilters(mode: $mode) {
...SavedFilterData
}

View File

@@ -1,6 +1,8 @@
query FindImages($filter: FindFilterType, $image_filter: ImageFilterType, $image_ids: [Int!]) {
findImages(filter: $filter, image_filter: $image_filter, image_ids: $image_ids) {
count
megapixels
filesize
images {
...SlimImageData
}

View File

@@ -1,8 +1,10 @@
query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) {
findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) {
count
filesize
duration
scenes {
...SlimSceneData
...SceneData
}
}
}
@@ -10,6 +12,8 @@ query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene
query FindScenesByPathRegex($filter: FindFilterType) {
findScenesByPathRegex(filter: $filter) {
count
filesize
duration
scenes {
...SlimSceneData
}
@@ -26,7 +30,9 @@ query FindScene($id: ID!, $checksum: String) {
findScene(id: $id, checksum: $checksum) {
...SceneData
}
}
query FindSceneMarkerTags($id: ID!) {
sceneMarkerTags(scene_id: $id) {
tag {
id
@@ -62,9 +68,11 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
}
query SceneStreams($id: ID!) {
sceneStreams(id: $id) {
url
mime_type
label
findScene(id: $id) {
sceneStreams {
url
mime_type
label
}
}
}

View File

@@ -1,27 +1,3 @@
query ScrapeFreeones($performer_name: String!) {
scrapeFreeones(performer_name: $performer_name) {
name
url
twitter
instagram
birthdate
ethnicity
country
eye_color
height
measurements
fake_tits
career_length
tattoos
piercings
aliases
details
death_date
hair_color
weight
}
}
query ScrapeFreeonesPerformers($q: String!) {
scrapeFreeonesPerformerList(query: $q)
}

View File

@@ -11,3 +11,10 @@ query Directory($path: String) {
directories
}
}
query ValidateStashBox($input: StashBoxInput!) {
validateStashBoxCredentials(input: $input) {
valid
status
}
}

View File

@@ -1,13 +1,14 @@
"""The query root for this schema"""
type Query {
# Filters
findSavedFilters(mode: FilterMode!): [SavedFilter!]!
findSavedFilter(id: ID!): SavedFilter
findSavedFilters(mode: FilterMode): [SavedFilter!]!
findDefaultFilter(mode: FilterMode!): SavedFilter
"""Find a scene by ID or Checksum"""
findScene(id: ID, checksum: String): Scene
findSceneByHash(input: SceneHashInput!): Scene
"""A function which queries Scene objects"""
findScenes(scene_filter: SceneFilterType, scene_ids: [Int!], filter: FindFilterType): FindScenesResultType!
@@ -25,7 +26,7 @@ type Query {
findSceneMarkers(scene_marker_filter: SceneMarkerFilterType filter: FindFilterType): FindSceneMarkersResultType!
findImage(id: ID, checksum: String): Image
"""A function which queries Scene objects"""
findImages(image_filter: ImageFilterType, image_ids: [Int!], filter: FindFilterType): FindImagesResultType!
@@ -67,10 +68,12 @@ type Query {
# Scrapers
"""List available scrapers"""
listPerformerScrapers: [Scraper!]!
listSceneScrapers: [Scraper!]!
listGalleryScrapers: [Scraper!]!
listMovieScrapers: [Scraper!]!
listScrapers(types: [ScrapeContentType!]!): [Scraper!]!
listPerformerScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [PERFORMER])")
listSceneScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [SCENE])")
listGalleryScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [GALLERY])")
listMovieScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [MOVIE])")
"""Scrape for a single scene"""
scrapeSingleScene(source: ScraperSourceInput!, input: ScrapeSingleSceneInput!): [ScrapedScene!]!
@@ -88,6 +91,9 @@ type Query {
"""Scrape for a single movie"""
scrapeSingleMovie(source: ScraperSourceInput!, input: ScrapeSingleMovieInput!): [ScrapedMovie!]!
"Scrapes content based on a URL"
scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent
"""Scrapes a complete performer record based on a URL"""
scrapePerformerURL(url: String!): ScrapedPerformer
"""Scrapes a complete performer record based on a URL"""
@@ -106,17 +112,9 @@ type Query {
"""Scrapes a complete gallery record based on an existing gallery"""
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery @deprecated(reason: "use scrapeSingleGallery")
"""Scrape a performer using Freeones"""
scrapeFreeones(performer_name: String!): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones")
"""Scrape a list of performers from a query"""
scrapeFreeonesPerformerList(query: String!): [String!]! @deprecated(reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones")
"""Query StashBox for scenes"""
queryStashBoxScene(input: StashBoxSceneQueryInput!): [ScrapedScene!]! @deprecated(reason: "use scrapeSingleScene or scrapeMultiScenes")
"""Query StashBox for performers"""
queryStashBoxPerformer(input: StashBoxPerformerQueryInput!): [StashBoxPerformerQueryResult!]! @deprecated(reason: "use scrapeSinglePerformer or scrapeMultiPerformers")
# === end deprecated methods ===
# Plugins
"""List loaded plugins"""
plugins: [Plugin!]
@@ -127,7 +125,13 @@ type Query {
"""Returns the current, complete configuration"""
configuration: ConfigResult!
"""Returns an array of paths for the given path"""
directory(path: String): Directory!
directory(
"The directory path to list"
path: String,
"Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..."
locale: String = "en"
): Directory!
validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult!
# System status
systemStatus: SystemStatus!
@@ -149,7 +153,7 @@ type Query {
# Version
version: Version!
# LatestVersion
latestversion: ShortVersion!
}
@@ -215,6 +219,7 @@ type Mutation {
movieUpdate(input: MovieUpdateInput!): Movie
movieDestroy(input: MovieDestroyInput!): Boolean!
moviesDestroy(ids: [ID!]!): Boolean!
bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag
@@ -232,6 +237,12 @@ type Mutation {
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult!
configureScraping(input: ConfigScrapingInput!): ConfigScrapingResult!
configureDefaults(input: ConfigDefaultSettingsInput!): ConfigDefaultSettingsResult!
# overwrites the entire UI configuration
configureUI(input: Map!): Map!
# sets a single UI key value
configureUISetting(key: String!, value: Any): Map!
"""Generate and set (or clear) API key"""
generateAPIKey(input: GenerateAPIKeyInput!): String!
@@ -254,6 +265,8 @@ type Mutation {
metadataAutoTag(input: AutoTagMetadataInput!): ID!
"""Clean metadata. Returns the job ID"""
metadataClean(input: CleanMetadataInput!): ID!
"""Identifies scenes using scrapers. Returns the job ID"""
metadataIdentify(input: IdentifyMetadataInput!): ID!
"""Migrate generated files for the current hash naming"""
migrateHashNaming: ID!
@@ -270,12 +283,17 @@ type Mutation {
"""Submit fingerprints to stash-box instance"""
submitStashBoxFingerprints(input: StashBoxFingerprintSubmissionInput!): Boolean!
"""Submit scene as draft to stash-box instance"""
submitStashBoxSceneDraft(input: StashBoxDraftSubmissionInput!): ID
"""Submit performer as draft to stash-box instance"""
submitStashBoxPerformerDraft(input: StashBoxDraftSubmissionInput!): ID
"""Backup the database. Optionally returns a link to download the database file"""
backupDatabase(input: BackupDatabaseInput!): String
"""Run batch performer tag task. Returns the job ID."""
stashBoxBatchPerformerTag(input: StashBoxBatchPerformerTagInput!): String!
"""Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default"""
enableDLNA(input: EnableDLNAInput!): Boolean!
"""Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default"""

View File

@@ -41,16 +41,18 @@ input ConfigGeneralInput {
generatedPath: String
"""Path to import/export files"""
metadataPath: String
"""Path to scrapers"""
scrapersPath: String
"""Path to cache"""
cachePath: String
"""Whether to calculate MD5 checksums for scene video files"""
calculateMD5: Boolean!
calculateMD5: Boolean
"""Hash algorithm to use for generated file naming"""
videoFileNamingAlgorithm: HashAlgorithm!
videoFileNamingAlgorithm: HashAlgorithm
"""Number of parallel tasks to start during scan/generate"""
parallelTasks: Int
"""Include audio stream in previews"""
previewAudio: Boolean!
previewAudio: Boolean
"""Number of segments in a preview file"""
previewSegments: Int
"""Preview segment duration, in seconds"""
@@ -74,17 +76,17 @@ input ConfigGeneralInput {
"""Maximum session cookie age"""
maxSessionAge: Int
"""Comma separated list of proxies to allow traffic from"""
trustedProxies: [String!]
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"""Name of the log file"""
logFile: String
"""Whether to also output to stderr"""
logOut: Boolean!
logOut: Boolean
"""Minimum log level"""
logLevel: String!
logLevel: String
"""Whether to log http access"""
logAccess: Boolean!
logAccess: Boolean
"""True if galleries should be created from folders with images"""
createGalleriesFromFolders: Boolean!
createGalleriesFromFolders: Boolean
"""Array of video file extensions"""
videoExtensions: [String!]
"""Array of image file extensions"""
@@ -104,7 +106,9 @@ input ConfigGeneralInput {
"""Whether the scraper should check for invalid certificates"""
scraperCertCheck: Boolean @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead")
"""Stash-box instances used for tagging"""
stashBoxes: [StashBoxInput!]!
stashBoxes: [StashBoxInput!]
"""Python path - resolved using path if unset"""
pythonPath: String
}
type ConfigGeneralResult {
@@ -155,7 +159,7 @@ type ConfigGeneralResult {
"""Maximum session cookie age"""
maxSessionAge: Int!
"""Comma separated list of proxies to allow traffic from"""
trustedProxies: [String!]!
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"""Name of the log file"""
logFile: String
"""Whether to also output to stderr"""
@@ -186,58 +190,150 @@ type ConfigGeneralResult {
scraperCertCheck: Boolean! @deprecated(reason: "use ConfigResult.scraping instead")
"""Stash-box instances used for tagging"""
stashBoxes: [StashBox!]!
"""Python path - resolved using path if unset"""
pythonPath: String!
}
input ConfigDisableDropdownCreateInput {
performer: Boolean
tag: Boolean
studio: Boolean
}
enum ImageLightboxDisplayMode {
ORIGINAL
FIT_XY
FIT_X
}
enum ImageLightboxScrollMode {
ZOOM
PAN_Y
}
input ConfigImageLightboxInput {
slideshowDelay: Int
displayMode: ImageLightboxDisplayMode
scaleUp: Boolean
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int
}
type ConfigImageLightboxResult {
slideshowDelay: Int
displayMode: ImageLightboxDisplayMode
scaleUp: Boolean
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int!
}
input ConfigInterfaceInput {
"""Ordered list of items that should be shown in the menu"""
menuItems: [String!]
"""Enable sound on mouseover previews"""
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Show scene scrubber by default"""
showScrubber: Boolean
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""If true, video will autostart on load in the scene player"""
autostartVideo: Boolean
"""If true, video will autostart when loading from play random or play selected"""
autostartVideoOnPlaySelected: Boolean
"""If true, next scene in playlist will be played at video end by default"""
continuePlaylistDefault: Boolean
"""If true, studio overlays will be shown as text instead of logo images"""
showStudioAsText: Boolean
"""Custom CSS"""
css: String
cssEnabled: Boolean
"""Interface language"""
language: String
"""Slideshow Delay"""
slideshowDelay: Int
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
imageLightbox: ConfigImageLightboxInput
"""Set to true to disable creating new objects via the dropdown menus"""
disableDropdownCreate: ConfigDisableDropdownCreateInput
"""Handy Connection Key"""
handyKey: String
"""Funscript Time Offset"""
funscriptOffset: Int
"""True if we should not auto-open a browser window on startup"""
noBrowser: Boolean
"""True if we should send notifications to the desktop"""
notificationsEnabled: Boolean
}
type ConfigDisableDropdownCreate {
performer: Boolean!
tag: Boolean!
studio: Boolean!
}
type ConfigInterfaceResult {
"""Ordered list of items that should be shown in the menu"""
menuItems: [String!]
"""Enable sound on mouseover previews"""
soundOnPreview: Boolean
"""Show title and tags in wall view"""
wallShowTitle: Boolean
"""Wall playback type"""
wallPlayback: String
"""Show scene scrubber by default"""
showScrubber: Boolean
"""Maximum duration (in seconds) in which a scene video will loop in the scene player"""
maximumLoopDuration: Int
"""True if we should not auto-open a browser window on startup"""
noBrowser: Boolean
"""True if we should send desktop notifications"""
notificationsEnabled: Boolean
"""If true, video will autostart on load in the scene player"""
autostartVideo: Boolean
"""If true, video will autostart when loading from play random or play selected"""
autostartVideoOnPlaySelected: Boolean
"""If true, next scene in playlist will be played at video end by default"""
continuePlaylistDefault: Boolean
"""If true, studio overlays will be shown as text instead of logo images"""
showStudioAsText: Boolean
"""Custom CSS"""
css: String
cssEnabled: Boolean
"""Interface language"""
language: String
"""Slideshow Delay"""
slideshowDelay: Int
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
imageLightbox: ConfigImageLightboxResult!
"""Fields are true if creating via dropdown menus are disabled"""
disableDropdownCreate: ConfigDisableDropdownCreate!
disabledDropdownCreate: ConfigDisableDropdownCreate! @deprecated(reason: "Use disableDropdownCreate")
"""Handy Connection Key"""
handyKey: String
"""Funscript Time Offset"""
@@ -270,7 +366,7 @@ input ConfigScrapingInput {
"""Scraper CDP path. Path to chrome executable or remote address"""
scraperCDPPath: String
"""Whether the scraper should check for invalid certificates"""
scraperCertCheck: Boolean!
scraperCertCheck: Boolean
"""Tags blacklist during scraping"""
excludeTagPatterns: [String!]
}
@@ -286,12 +382,38 @@ type ConfigScrapingResult {
excludeTagPatterns: [String!]!
}
type ConfigDefaultSettingsResult {
scan: ScanMetadataOptions
identify: IdentifyMetadataTaskOptions
autoTag: AutoTagMetadataOptions
generate: GenerateMetadataOptions
"""If true, delete file checkbox will be checked by default"""
deleteFile: Boolean
"""If true, delete generated supporting files checkbox will be checked by default"""
deleteGenerated: Boolean
}
input ConfigDefaultSettingsInput {
scan: ScanMetadataInput
identify: IdentifyMetadataInput
autoTag: AutoTagMetadataInput
generate: GenerateMetadataInput
"""If true, delete file checkbox will be checked by default"""
deleteFile: Boolean
"""If true, delete generated files checkbox will be checked by default"""
deleteGenerated: Boolean
}
"""All configuration settings"""
type ConfigResult {
general: ConfigGeneralResult!
interface: ConfigInterfaceResult!
dlna: ConfigDLNAResult!
scraping: ConfigScrapingResult!
defaults: ConfigDefaultSettingsResult!
ui: Map!
}
"""Directory structure of a path"""
@@ -317,3 +439,8 @@ type StashConfig {
input GenerateAPIKeyInput {
clear: Boolean
}
type StashBoxValidationResult {
valid: Boolean!
status: String!
}

View File

@@ -33,6 +33,12 @@ input ResolutionCriterionInput {
modifier: CriterionModifier!
}
input PHashDuplicationCriterionInput {
duplicated: Boolean
"""Currently unimplemented"""
distance: Int
}
input PerformerFilterType {
AND: PerformerFilterType
OR: PerformerFilterType
@@ -95,6 +101,8 @@ input PerformerFilterType {
death_year: IntCriterionInput
"""Filter by studios where performer appears in scene/image/gallery"""
studios: HierarchicalMultiCriterionInput
"""Filter by autotag ignore value"""
ignore_auto_tag: Boolean
}
input SceneMarkerFilterType {
@@ -130,6 +138,8 @@ input SceneFilterType {
organized: Boolean
"""Filter by o-counter"""
o_counter: IntCriterionInput
"""Filter Scenes that have an exact phash match available"""
duplicated: PHashDuplicationCriterionInput
"""Filter by resolution"""
resolution: ResolutionCriterionInput
"""Filter by duration (in seconds)"""
@@ -148,6 +158,10 @@ input SceneFilterType {
tag_count: IntCriterionInput
"""Filter to only include scenes with performers with these tags"""
performer_tags: HierarchicalMultiCriterionInput
"""Filter scenes that have performers that have been favorited"""
performer_favorite: Boolean
"""Filter scenes by performer age at time of scene"""
performer_age: IntCriterionInput
"""Filter to only include scenes with these performers"""
performers: MultiCriterionInput
"""Filter by performer count"""
@@ -158,6 +172,10 @@ input SceneFilterType {
url: StringCriterionInput
"""Filter by interactive"""
interactive: Boolean
"""Filter by InteractiveSpeed"""
interactive_speed: IntCriterionInput
"""Filter by captions"""
captions: StringCriterionInput
}
input MovieFilterType {
@@ -205,6 +223,8 @@ input StudioFilterType {
url: StringCriterionInput
"""Filter by studio aliases"""
aliases: StringCriterionInput
"""Filter by autotag ignore value"""
ignore_auto_tag: Boolean
}
input GalleryFilterType {
@@ -241,6 +261,10 @@ input GalleryFilterType {
performers: MultiCriterionInput
"""Filter by performer count"""
performer_count: IntCriterionInput
"""Filter galleries that have performers that have been favorited"""
performer_favorite: Boolean
"""Filter galleries by performer age at time of gallery"""
performer_age: IntCriterionInput
"""Filter by number of images in this gallery"""
image_count: IntCriterionInput
"""Filter by url"""
@@ -287,6 +311,9 @@ input TagFilterType {
"""Filter by number f child tags the tag has"""
child_count: IntCriterionInput
"""Filter by autotag ignore value"""
ignore_auto_tag: Boolean
}
input ImageFilterType {
@@ -322,6 +349,8 @@ input ImageFilterType {
performers: MultiCriterionInput
"""Filter by performer count"""
performer_count: IntCriterionInput
"""Filter images that have performers that have been favorited"""
performer_favorite: Boolean
"""Filter to only include images with these galleries"""
galleries: MultiCriterionInput
}

View File

@@ -74,6 +74,11 @@ input BulkGalleryUpdateInput {
input GalleryDestroyInput {
ids: [ID!]!
"""
If true, then the zip file will be deleted if the gallery is zip-file-based.
If gallery is folder-based, then any files not associated with other
galleries will be deleted, along with the folder, if it is not empty.
"""
delete_file: Boolean
delete_generated: Boolean
}

View File

@@ -70,5 +70,9 @@ input ImagesDestroyInput {
type FindImagesResultType {
count: Int!
"""Total megapixels of the images"""
megapixels: Float!
"""Total file size in bytes"""
filesize: Float!
images: [Image!]!
}

View File

@@ -2,6 +2,7 @@
scalar Time
enum LogLevel {
Trace
Debug
Info
Progress

View File

@@ -9,7 +9,10 @@ input GenerateMetadataInput {
markerImagePreviews: Boolean
markerScreenshots: Boolean
transcodes: Boolean
"""Generate transcodes even if not required"""
forceTranscodes: Boolean
phashes: Boolean
interactiveHeatmapsSpeeds: Boolean
"""scene ids to generate for"""
sceneIDs: [ID!]
@@ -33,8 +36,41 @@ input GeneratePreviewOptionsInput {
previewPreset: PreviewPreset
}
type GenerateMetadataOptions {
sprites: Boolean
previews: Boolean
imagePreviews: Boolean
previewOptions: GeneratePreviewOptions
markers: Boolean
markerImagePreviews: Boolean
markerScreenshots: Boolean
transcodes: Boolean
phashes: Boolean
interactiveHeatmapsSpeeds: Boolean
}
type GeneratePreviewOptions {
"""Number of segments in a preview file"""
previewSegments: Int
"""Preview segment duration, in seconds"""
previewSegmentDuration: Float
"""Duration of start of video to exclude when generating previews"""
previewExcludeStart: String
"""Duration of end of video to exclude when generating previews"""
previewExcludeEnd: String
"""Preset when generating preview"""
previewPreset: PreviewPreset
}
"Filter options for meta data scannning"
input ScanMetaDataFilterInput {
"If set, files with a modification time before this time point are ignored by the scan"
minModTime: Timestamp
}
input ScanMetadataInput {
paths: [String!]
"""Set name, date, details from metadata (if present)"""
useFileMetadata: Boolean
"""Strip file extension from title"""
@@ -49,9 +85,31 @@ input ScanMetadataInput {
scanGeneratePhashes: Boolean
"""Generate image thumbnails during scan"""
scanGenerateThumbnails: Boolean
"Filter options for the scan"
filter: ScanMetaDataFilterInput
}
type ScanMetadataOptions {
"""Set name, date, details from metadata (if present)"""
useFileMetadata: Boolean!
"""Strip file extension from title"""
stripFileExtension: Boolean!
"""Generate previews during scan"""
scanGeneratePreviews: Boolean!
"""Generate image previews during scan"""
scanGenerateImagePreviews: Boolean!
"""Generate sprites during scan"""
scanGenerateSprites: Boolean!
"""Generate phashes during scan"""
scanGeneratePhashes: Boolean!
"""Generate image thumbnails during scan"""
scanGenerateThumbnails: Boolean!
}
input CleanMetadataInput {
paths: [String!]
"""Do a dry run. Don't delete any files"""
dryRun: Boolean!
}
@@ -67,6 +125,97 @@ input AutoTagMetadataInput {
tags: [String!]
}
type AutoTagMetadataOptions {
"""IDs of performers to tag files with, or "*" for all"""
performers: [String!]
"""IDs of studios to tag files with, or "*" for all"""
studios: [String!]
"""IDs of tags to tag files with, or "*" for all"""
tags: [String!]
}
enum IdentifyFieldStrategy {
"""Never sets the field value"""
IGNORE
"""
For multi-value fields, merge with existing.
For single-value fields, ignore if already set
"""
MERGE
"""Always replaces the value if a value is found.
For multi-value fields, any existing values are removed and replaced with the
scraped values.
"""
OVERWRITE
}
input IdentifyFieldOptionsInput {
field: String!
strategy: IdentifyFieldStrategy!
"""creates missing objects if needed - only applicable for performers, tags and studios"""
createMissing: Boolean
}
input IdentifyMetadataOptionsInput {
"""any fields missing from here are defaulted to MERGE and createMissing false"""
fieldOptions: [IdentifyFieldOptionsInput!]
"""defaults to true if not provided"""
setCoverImage: Boolean
setOrganized: Boolean
"""defaults to true if not provided"""
includeMalePerformers: Boolean
}
input IdentifySourceInput {
source: ScraperSourceInput!
"""Options defined for a source override the defaults"""
options: IdentifyMetadataOptionsInput
}
input IdentifyMetadataInput {
"""An ordered list of sources to identify items with. Only the first source that finds a match is used."""
sources: [IdentifySourceInput!]!
"""Options defined here override the configured defaults"""
options: IdentifyMetadataOptionsInput
"""scene ids to identify"""
sceneIDs: [ID!]
"""paths of scenes to identify - ignored if scene ids are set"""
paths: [String!]
}
# types for default options
type IdentifyFieldOptions {
field: String!
strategy: IdentifyFieldStrategy!
"""creates missing objects if needed - only applicable for performers, tags and studios"""
createMissing: Boolean
}
type IdentifyMetadataOptions {
"""any fields missing from here are defaulted to MERGE and createMissing false"""
fieldOptions: [IdentifyFieldOptions!]
"""defaults to true if not provided"""
setCoverImage: Boolean
setOrganized: Boolean
"""defaults to true if not provided"""
includeMalePerformers: Boolean
}
type IdentifySource {
source: ScraperSource!
"""Options defined for a source override the defaults"""
options: IdentifyMetadataOptions
}
type IdentifyMetadataTaskOptions {
"""An ordered list of sources to identify items with. Only the first source that finds a match is used."""
sources: [IdentifySource!]!
"""Options defined here override the configured defaults"""
options: IdentifyMetadataOptions
}
input ExportObjectTypeInput {
ids: [String!]
all: Boolean

View File

@@ -54,6 +54,14 @@ input MovieUpdateInput {
back_image: String
}
input BulkMovieUpdateInput {
clientMutationId: String
ids: [ID!]
rating: Int
studio_id: ID
director: String
}
input MovieDestroyInput {
id: ID!
}

View File

@@ -28,6 +28,7 @@ type Performer {
aliases: String
favorite: Boolean!
tags: [Tag!]!
ignore_auto_tag: Boolean!
image_path: String # Resolver
scene_count: Int # Resolver
@@ -73,6 +74,7 @@ input PerformerCreateInput {
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input PerformerUpdateInput {
@@ -103,6 +105,7 @@ input PerformerUpdateInput {
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input BulkPerformerUpdateInput {
@@ -130,6 +133,7 @@ input BulkPerformerUpdateInput {
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input PerformerDestroyInput {

View File

@@ -0,0 +1,12 @@
"""
Timestamp is a point in time. It is always output as RFC3339-compatible time points.
It can be input as a RFC3339 string, or as "<4h" for "4 hours in the past" or ">5m"
for "5 minutes in the future"
"""
scalar Timestamp
# generic JSON object
scalar Map
scalar Any

View File

@@ -18,6 +18,8 @@ type ScenePathsType {
chapters_vtt: String # Resolver
sprite: String # Resolver
funscript: String # Resolver
interactive_heatmap: String # Resolver
caption: String # Resolver
}
type SceneMovie {
@@ -25,6 +27,11 @@ type SceneMovie {
scene_index: Int
}
type SceneCaption {
language_code: String!
caption_type: String!
}
type Scene {
id: ID!
checksum: String
@@ -39,6 +46,8 @@ type Scene {
path: String!
phash: String
interactive: Boolean!
interactive_speed: Int
captions: [SceneCaption!]
created_at: Time!
updated_at: Time!
file_mod_time: Time
@@ -53,6 +62,9 @@ type Scene {
tags: [Tag!]!
performers: [Performer!]!
stash_ids: [StashID!]!
"""Return valid stream paths"""
sceneStreams: [SceneStreamEndpoint!]!
}
input SceneMovieInput {
@@ -120,6 +132,10 @@ input ScenesDestroyInput {
type FindScenesResultType {
count: Int!
"""Total duration in seconds"""
duration: Float!
"""Total file size in bytes"""
filesize: Float!
scenes: [Scene!]!
}

View File

@@ -1,5 +1,5 @@
enum ScrapeType {
"""From text query"""
"""From text query"""
NAME
"""From existing object"""
FRAGMENT
@@ -7,6 +7,22 @@ enum ScrapeType {
URL
}
"Type of the content a scraper generates"
enum ScrapeContentType {
GALLERY
MOVIE
PERFORMER
SCENE
}
"Scraped Content is the forming union over the different scrapers"
union ScrapedContent = ScrapedStudio
| ScrapedTag
| ScrapedScene
| ScrapedGallery
| ScrapedMovie
| ScrapedPerformer
type ScraperSpec {
"""URLs matching these can be scraped with"""
urls: [String!]
@@ -26,11 +42,13 @@ type Scraper {
movie: ScraperSpec
}
type ScrapedStudio {
"""Set if studio matched"""
stored_id: ID
name: String!
url: String
image: String
remote_site_id: String
}
@@ -95,7 +113,18 @@ input ScrapedGalleryInput {
input ScraperSourceInput {
"""Index of the configured stash-box instance to use. Should be unset if scraper_id is set"""
stash_box_index: Int
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
"""Stash-box endpoint"""
stash_box_endpoint: String
"""Scraper ID to scrape with. Should be unset if stash_box_index is set"""
scraper_id: ID
}
type ScraperSource {
"""Index of the configured stash-box instance to use. Should be unset if scraper_id is set"""
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
"""Stash-box endpoint"""
stash_box_endpoint: String
"""Scraper ID to scrape with. Should be unset if stash_box_index is set"""
scraper_id: ID
}
@@ -175,10 +204,16 @@ type StashBoxFingerprint {
duration: Int!
}
"""If neither performer_ids nor performer_names are set, tag all performers"""
input StashBoxBatchPerformerTagInput {
"Stash endpoint to use for the performer tagging"
endpoint: Int!
"Fields to exclude when executing the performer tagging"
exclude_fields: [String!]
"Refresh performers already tagged by StashBox if true. Only tag performers with no StashBox tagging if false"
refresh: Boolean!
"If set, only tag these performer ids"
performer_ids: [ID!]
"If set, only tag these performer names"
performer_names: [String!]
}

View File

@@ -24,3 +24,8 @@ input StashBoxFingerprintSubmissionInput {
scene_ids: [String!]!
stash_box_index: Int!
}
input StashBoxDraftSubmissionInput {
id: String!
stash_box_index: Int!
}

View File

@@ -6,6 +6,7 @@ type Studio {
parent_studio: Studio
child_studios: [Studio!]!
aliases: [String!]!
ignore_auto_tag: Boolean!
image_path: String # Resolver
scene_count: Int # Resolver
@@ -30,6 +31,7 @@ input StudioCreateInput {
rating: Int
details: String
aliases: [String!]
ignore_auto_tag: Boolean
}
input StudioUpdateInput {
@@ -43,6 +45,7 @@ input StudioUpdateInput {
rating: Int
details: String
aliases: [String!]
ignore_auto_tag: Boolean
}
input StudioDestroyInput {

View File

@@ -2,6 +2,7 @@ type Tag {
id: ID!
name: String!
aliases: [String!]!
ignore_auto_tag: Boolean!
created_at: Time!
updated_at: Time!
@@ -19,6 +20,7 @@ type Tag {
input TagCreateInput {
name: String!
aliases: [String!]
ignore_auto_tag: Boolean
"""This should be a URL or a base64 encoded data URL"""
image: String
@@ -31,6 +33,7 @@ input TagUpdateInput {
id: ID!
name: String
aliases: [String!]
ignore_auto_tag: Boolean
"""This should be a URL or a base64 encoded data URL"""
image: String

View File

@@ -49,6 +49,7 @@ fragment PerformerFragment on Performer {
disambiguation
aliases
gender
merged_ids
urls {
...URLFragment
}
@@ -75,11 +76,6 @@ fragment PerformerFragment on Performer {
piercings {
...BodyModificationFragment
}
details
death_date {
...FuzzyDateFragment
}
weight
}
fragment PerformerAppearanceFragment on PerformerAppearance {
@@ -127,8 +123,14 @@ query FindSceneByFingerprint($fingerprint: FingerprintQueryInput!) {
}
}
query FindScenesByFingerprints($fingerprints: [String!]!) {
findScenesByFingerprints(fingerprints: $fingerprints) {
query FindScenesByFullFingerprints($fingerprints: [FingerprintQueryInput!]!) {
findScenesByFullFingerprints(fingerprints: $fingerprints) {
...SceneFragment
}
}
query FindScenesBySceneFingerprints($fingerprints: [[FingerprintQueryInput!]!]!) {
findScenesBySceneFingerprints(fingerprints: $fingerprints) {
...SceneFragment
}
}
@@ -151,6 +153,30 @@ query FindPerformerByID($id: ID!) {
}
}
query FindSceneByID($id: ID!) {
findScene(id: $id) {
...SceneFragment
}
}
mutation SubmitFingerprint($input: FingerprintSubmission!) {
submitFingerprint(input: $input)
}
query Me {
me {
name
}
}
mutation SubmitSceneDraft($input: SceneDraftInput!) {
submitSceneDraft(input: $input) {
id
}
}
mutation SubmitPerformerDraft($input: PerformerDraftInput!) {
submitPerformerDraft(input: $input) {
id
}
}

View File

@@ -1,14 +1,15 @@
package api
import (
"errors"
"net"
"net/http"
"net/url"
"strings"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/session"
)
@@ -25,7 +26,8 @@ const (
)
func allowUnauthenticated(r *http.Request) bool {
return strings.HasPrefix(r.URL.Path, loginEndPoint) || r.URL.Path == "/css"
// #2715 - allow access to UI files
return strings.HasPrefix(r.URL.Path, loginEndPoint) || r.URL.Path == "/css" || strings.HasPrefix(r.URL.Path, "/assets")
}
func authenticateHandler() func(http.Handler) http.Handler {
@@ -39,7 +41,7 @@ func authenticateHandler() func(http.Handler) http.Handler {
userID, err := manager.GetInstance().SessionStore.Authenticate(w, r)
if err != nil {
if err != session.ErrUnauthorized {
if errors.Is(err, session.ErrUnauthorized) {
w.WriteHeader(http.StatusInternalServerError)
_, err = w.Write([]byte(err.Error()))
if err != nil {
@@ -55,16 +57,13 @@ func authenticateHandler() func(http.Handler) http.Handler {
}
if err := session.CheckAllowPublicWithoutAuth(c, r); err != nil {
switch err := err.(type) {
case session.ExternalAccessError:
securityActivateTripwireAccessedFromInternetWithoutAuth(c, err, w)
return
case session.UntrustedProxyError:
logger.Warnf("Rejected request from untrusted proxy: %s", net.IP(err).String())
w.WriteHeader(http.StatusForbidden)
var externalAccess session.ExternalAccessError
switch {
case errors.As(err, &externalAccess):
securityActivateTripwireAccessedFromInternetWithoutAuth(c, externalAccess, w)
return
default:
logger.Errorf("Error checking external access security: %s", err.Error())
logger.Errorf("Error checking external access security: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -132,9 +131,4 @@ func securityActivateTripwireAccessedFromInternetWithoutAuth(c *config.Instance,
if err != nil {
logger.Error(err)
}
err = manager.GetInstance().Shutdown()
if err != nil {
logger.Error(err)
}
}

View File

@@ -1,22 +1,22 @@
package utils
package api
import (
"strconv"
"strings"
)
type ByteRange struct {
type byteRange struct {
Start int64
End *int64
RawString string
}
func CreateByteRange(s string) ByteRange {
func createByteRange(s string) byteRange {
// strip bytes=
r := strings.TrimPrefix(s, "bytes=")
e := strings.Split(r, "-")
ret := ByteRange{
ret := byteRange{
RawString: s,
}
if len(e) > 0 {
@@ -30,7 +30,7 @@ func CreateByteRange(s string) ByteRange {
return ret
}
func (r ByteRange) ToHeaderValue(fileLength int64) string {
func (r byteRange) toHeaderValue(fileLength int64) string {
if r.End == nil {
return ""
}
@@ -38,7 +38,7 @@ func (r ByteRange) ToHeaderValue(fileLength int64) string {
return "bytes " + strconv.FormatInt(r.Start, 10) + "-" + strconv.FormatInt(end, 10) + "/" + strconv.FormatInt(fileLength, 10)
}
func (r ByteRange) Apply(bytes []byte) []byte {
func (r byteRange) apply(bytes []byte) []byte {
if r.End == nil {
return bytes[r.Start:]
}

View File

@@ -1,6 +1,7 @@
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
@@ -15,7 +16,7 @@ import (
"github.com/stashapp/stash/pkg/logger"
)
//we use the github REST V3 API as no login is required
// we use the github REST V3 API as no login is required
const apiReleases string = "https://api.github.com/repos/stashapp/stash/releases"
const apiTags string = "https://api.github.com/repos/stashapp/stash/tags"
const apiAcceptHeader string = "application/vnd.github.v3+json"
@@ -32,7 +33,7 @@ var stashReleases = func() map[string]string {
"darwin/arm64": "stash-osx-applesilicon",
"linux/amd64": "stash-linux",
"windows/amd64": "stash-win.exe",
"linux/arm": "stash-pi",
"linux/arm": "stash-linux-arm32v6",
"linux/arm64": "stash-linux-arm64v8",
"linux/armv7": "stash-linux-arm32v7",
}
@@ -107,19 +108,19 @@ type githubTagResponse struct {
Node_id string
}
func makeGithubRequest(url string, output interface{}) error {
func makeGithubRequest(ctx context.Context, url string, output interface{}) error {
client := &http.Client{
Timeout: 3 * time.Second,
}
req, _ := http.NewRequest("GET", url, nil)
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Add("Accept", apiAcceptHeader) // gh api recommendation , send header with api version
response, err := client.Do(req)
if err != nil {
//lint:ignore ST1005 Github is a proper capitalized noun
return fmt.Errorf("Github API request failed: %s", err)
return fmt.Errorf("Github API request failed: %w", err)
}
if response.StatusCode != http.StatusOK {
@@ -132,12 +133,12 @@ func makeGithubRequest(url string, output interface{}) error {
data, err := io.ReadAll(response.Body)
if err != nil {
//lint:ignore ST1005 Github is a proper capitalized noun
return fmt.Errorf("Github API read response failed: %s", err)
return fmt.Errorf("Github API read response failed: %w", err)
}
err = json.Unmarshal(data, output)
if err != nil {
return fmt.Errorf("unmarshalling Github API response failed: %s", err)
return fmt.Errorf("unmarshalling Github API response failed: %w", err)
}
return nil
@@ -147,7 +148,7 @@ func makeGithubRequest(url string, output interface{}) error {
// If running a build from the "master" branch, then the latest full release
// is used, otherwise it uses the release that is tagged with "latest_develop"
// which is the latest pre-release build.
func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease string, err error) {
func GetLatestVersion(ctx context.Context, shortHash bool) (latestVersion string, latestRelease string, err error) {
arch := runtime.GOARCH // https://en.wikipedia.org/wiki/Comparison_of_ARM_cores
isARMv7 := cpu.ARM.HasNEON || cpu.ARM.HasVFPv3 || cpu.ARM.HasVFPv3D16 || cpu.ARM.HasVFPv4 // armv6 doesn't support any of these features
@@ -180,14 +181,14 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
}
release := githubReleasesResponse{}
err = makeGithubRequest(url, &release)
err = makeGithubRequest(ctx, url, &release)
if err != nil {
return "", "", err
}
if release.Prerelease == usePreRelease {
latestVersion = getReleaseHash(release, shortHash, usePreRelease)
latestVersion = getReleaseHash(ctx, release, shortHash, usePreRelease)
if wantedRelease != "" {
for _, asset := range release.Assets {
@@ -205,12 +206,12 @@ func GetLatestVersion(shortHash bool) (latestVersion string, latestRelease strin
return latestVersion, latestRelease, nil
}
func getReleaseHash(release githubReleasesResponse, shortHash bool, usePreRelease bool) string {
func getReleaseHash(ctx context.Context, release githubReleasesResponse, shortHash bool, usePreRelease bool) string {
shaLength := len(release.Target_commitish)
// the /latest API call doesn't return the hash in target_commitish
// also add sanity check in case Target_commitish is not 40 characters
if !usePreRelease || shaLength != 40 {
return getShaFromTags(shortHash, release.Tag_name)
return getShaFromTags(ctx, shortHash, release.Tag_name)
}
if shortHash {
@@ -225,9 +226,9 @@ func getReleaseHash(release githubReleasesResponse, shortHash bool, usePreReleas
return release.Target_commitish
}
func printLatestVersion() {
func printLatestVersion(ctx context.Context) {
_, githash, _ = GetVersion()
latest, _, err := GetLatestVersion(true)
latest, _, err := GetLatestVersion(ctx, true)
if err != nil {
logger.Errorf("Couldn't find latest version: %s", err)
} else {
@@ -241,13 +242,21 @@ func printLatestVersion() {
// get sha from the github api tags endpoint
// returns the sha1 hash/shorthash or "" if something's wrong
func getShaFromTags(shortHash bool, name string) string {
func getShaFromTags(ctx context.Context, shortHash bool, name string) string {
url := apiTags
tags := []githubTagResponse{}
err := makeGithubRequest(url, &tags)
err := makeGithubRequest(ctx, url, &tags)
if err != nil {
logger.Errorf("Github Tags Api %v", err)
// If the context is canceled, we don't want to log this as an error
// in the path. The function here just gives up and returns "" if
// something goes wrong. Hence, log the error at the info-level so
// it's still present, but don't treat this as an error.
if errors.Is(err, context.Canceled) {
logger.Infof("aborting sha request due to context cancellation")
} else {
logger.Errorf("Github Tags Api: %v", err)
}
return ""
}
_, gitShort, _ := GetVersion() // retrieve short hash to check actual length

48
internal/api/dir_list.go Normal file
View File

@@ -0,0 +1,48 @@
package api
import (
"io/fs"
"os"
"path/filepath"
"golang.org/x/text/collate"
)
type dirLister []fs.DirEntry
func (s dirLister) Len() int {
return len(s)
}
func (s dirLister) Swap(i, j int) {
s[j], s[i] = s[i], s[j]
}
func (s dirLister) Bytes(i int) []byte {
return []byte(s[i].Name())
}
// listDir will return the contents of a given directory path as a string slice
func listDir(col *collate.Collator, path string) ([]string, error) {
var dirPaths []string
files, err := os.ReadDir(path)
if err != nil {
path = filepath.Dir(path)
files, err = os.ReadDir(path)
if err != nil {
return dirPaths, err
}
}
if col != nil {
col.Sort(dirLister(files))
}
for _, file := range files {
if !file.IsDir() {
continue
}
dirPaths = append(dirPaths, filepath.Join(path, file.Name()))
}
return dirPaths, nil
}

View File

@@ -6,10 +6,10 @@ import (
"os"
"strings"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/internal/static"
"github.com/stashapp/stash/pkg/hash"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/static"
"github.com/stashapp/stash/pkg/utils"
)
type imageBox struct {
@@ -17,14 +17,31 @@ type imageBox struct {
files []string
}
var imageExtensions = []string{
".jpg",
".jpeg",
".png",
".gif",
".svg",
".webp",
}
func newImageBox(box fs.FS) (*imageBox, error) {
ret := &imageBox{
box: box,
}
err := fs.WalkDir(box, ".", func(path string, d fs.DirEntry, err error) error {
if !d.IsDir() {
ret.files = append(ret.files, path)
if d.IsDir() {
return nil
}
baseName := strings.ToLower(d.Name())
for _, ext := range imageExtensions {
if strings.HasSuffix(baseName, ext) {
ret.files = append(ret.files, path)
break
}
}
return nil
@@ -85,7 +102,7 @@ func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte,
}
imageFiles := box.files
index := utils.IntFromString(name) % uint64(len(imageFiles))
index := hash.IntFromString(name) % uint64(len(imageFiles))
img, err := box.box.Open(imageFiles[index])
if err != nil {
return nil, err

42
internal/api/locale.go Normal file
View File

@@ -0,0 +1,42 @@
package api
import (
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
// matcher defines a matcher for the languages we support
var matcher = language.NewMatcher([]language.Tag{
language.MustParse("en-US"), // The first language is used as fallback.
language.MustParse("en-GB"),
language.MustParse("en-AU"),
language.MustParse("es-ES"),
language.MustParse("de-DE"),
language.MustParse("it-IT"),
language.MustParse("fr-FR"),
language.MustParse("fi-FI"),
language.MustParse("pt-BR"),
language.MustParse("sv-SE"),
language.MustParse("zh-CN"),
language.MustParse("zh-TW"),
language.MustParse("hr-HR"),
language.MustParse("nl-NL"),
language.MustParse("ru-RU"),
language.MustParse("tr-TR"),
language.MustParse("da-DK"),
language.MustParse("pl-PL"),
language.MustParse("ko-KR"),
})
// newCollator parses a locale into a collator
// Go through the available matches and return a valid match, in practice the first is a fallback
// Optionally pass collation options through for creation.
// If passed a nil-locale string, return nil
func newCollator(locale *string, opts ...collate.Option) *collate.Collator {
if locale == nil {
return nil
}
tag, _ := language.MatchStrings(matcher, *locale)
return collate.New(tag, opts...)
}

View File

@@ -2,12 +2,27 @@ package api
import (
"context"
"errors"
"sort"
"strconv"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/scraper"
)
var (
// ErrNotImplemented is an error which means the given functionality isn't implemented by the API.
ErrNotImplemented = errors.New("not implemented")
// ErrNotSupported is returned whenever there's a test, which can be used to guard against the error,
// but the given parameters aren't supported by the system.
ErrNotSupported = errors.New("not supported")
// ErrInput signifies errors where the input isn't valid for some reason. And no more specific error exists.
ErrInput = errors.New("input error")
)
type hookExecutor interface {
@@ -19,6 +34,10 @@ type Resolver struct {
hookExecutor hookExecutor
}
func (r *Resolver) scraperCache() *scraper.Cache {
return manager.GetInstance().ScraperCache
}
func (r *Resolver) Gallery() models.GalleryResolver {
return &galleryResolver{r}
}
@@ -158,9 +177,9 @@ func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) {
}, nil
}
//Gets latest version (git shorthash commit for now)
// Latestversion returns the latest git shorthash commit.
func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion, error) {
ver, url, err := GetLatestVersion(true)
ver, url, err := GetLatestVersion(ctx, true)
if err == nil {
logger.Infof("Retrieved latest hash: %s", ver)
} else {

View File

@@ -26,7 +26,14 @@ func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*stri
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret []*models.Image, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var err error
// #2376 - sort images by path
// doing this via Query is really slow, so stick with FindByGalleryID
ret, err = repo.Image().FindByGalleryID(obj.ID)
if err != nil {
return err
}
return err
}); err != nil {
return nil, err
@@ -37,6 +44,7 @@ func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
// doing this via Query is really slow, so stick with FindByGalleryID
imgs, err := repo.Image().FindByGalleryID(obj.ID)
if err != nil {
return err

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"

View File

@@ -4,8 +4,8 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
@@ -61,6 +61,14 @@ func (r *sceneResolver) Rating(ctx context.Context, obj *models.Scene) (*int, er
return nil, nil
}
func (r *sceneResolver) InteractiveSpeed(ctx context.Context, obj *models.Scene) (*int, error) {
if obj.InteractiveSpeed.Valid {
interactive_speed := int(obj.InteractiveSpeed.Int64)
return &interactive_speed, nil
}
return nil, nil
}
func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.SceneFileType, error) {
width := int(obj.Width.Int64)
height := int(obj.Height.Int64)
@@ -79,8 +87,9 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc
func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.ScenePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
config := manager.GetInstance().Config
builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID)
builder.APIKey = config.GetInstance().GetAPIKey()
builder.APIKey = config.GetAPIKey()
screenshotPath := builder.GetScreenshotURL(obj.UpdatedAt.Timestamp)
previewPath := builder.GetStreamPreviewURL()
streamPath := builder.GetStreamURL()
@@ -89,16 +98,20 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.S
spritePath := builder.GetSpriteURL()
chaptersVttPath := builder.GetChaptersVTTURL()
funscriptPath := builder.GetFunscriptURL()
captionBasePath := builder.GetCaptionURL()
interactiveHeatmap := builder.GetInteractiveHeatmapURL()
return &models.ScenePathsType{
Screenshot: &screenshotPath,
Preview: &previewPath,
Stream: &streamPath,
Webp: &webpPath,
Vtt: &vttPath,
ChaptersVtt: &chaptersVttPath,
Sprite: &spritePath,
Funscript: &funscriptPath,
Screenshot: &screenshotPath,
Preview: &previewPath,
Stream: &streamPath,
Webp: &webpPath,
Vtt: &vttPath,
ChaptersVtt: &chaptersVttPath,
Sprite: &spritePath,
Funscript: &funscriptPath,
InteractiveHeatmap: &interactiveHeatmap,
Caption: &captionBasePath,
}, nil
}
@@ -113,6 +126,17 @@ func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (re
return ret, nil
}
func (r *sceneResolver) Captions(ctx context.Context, obj *models.Scene) (ret []*models.SceneCaption, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Scene().GetCaptions(obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, err
}
func (r *sceneResolver) Galleries(ctx context.Context, obj *models.Scene) (ret []*models.Gallery, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Gallery().FindBySceneID(obj.ID)
@@ -140,7 +164,7 @@ func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *mod
}
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*models.SceneMovie, err error) {
if err := r.withTxn(ctx, func(repo models.Repository) error {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
qb := repo.Scene()
mqb := repo.Movie()
@@ -227,3 +251,12 @@ func (r *sceneResolver) UpdatedAt(ctx context.Context, obj *models.Scene) (*time
func (r *sceneResolver) FileModTime(ctx context.Context, obj *models.Scene) (*time.Time, error) {
return &obj.FileModTime.Timestamp, nil
}
func (r *sceneResolver) SceneStreams(ctx context.Context, obj *models.Scene) ([]*models.SceneStreamEndpoint, error) {
config := manager.GetInstance().Config
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID)
return manager.GetSceneStreamPaths(obj, builder.GetStreamURL(), config.GetMaxStreamingTranscodeSize())
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
@@ -39,7 +39,7 @@ func (r *studioResolver) ImagePath(ctx context.Context, obj *models.Studio) (*st
// indicate that image is missing by setting default query param to true
if !hasImage {
imagePath = imagePath + "?default=true"
imagePath += "?default=true"
}
return &imagePath, nil

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/gallery"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"

View File

@@ -6,25 +6,28 @@ import (
"fmt"
"path/filepath"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
var ErrOverriddenConfig = errors.New("cannot set overridden value")
func (r *mutationResolver) Setup(ctx context.Context, input models.SetupInput) (bool, error) {
err := manager.GetInstance().Setup(input)
err := manager.GetInstance().Setup(ctx, input)
return err == nil, err
}
func (r *mutationResolver) Migrate(ctx context.Context, input models.MigrateInput) (bool, error) {
err := manager.GetInstance().Migrate(input)
err := manager.GetInstance().Migrate(ctx, input)
return err == nil, err
}
func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) {
c := config.GetInstance()
existingPaths := c.GetStashPaths()
if len(input.Stashes) > 0 {
for _, s := range input.Stashes {
@@ -37,7 +40,7 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
}
}
if isNew {
exists, err := utils.DirExists(s.Path)
exists, err := fsutil.DirExists(s.Path)
if !exists {
return makeConfigGeneralResult(), err
}
@@ -46,7 +49,34 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
c.Set(config.Stash, input.Stashes)
}
if input.DatabasePath != nil {
checkConfigOverride := func(key string) error {
if c.HasOverride(key) {
return fmt.Errorf("%w: %s", ErrOverriddenConfig, key)
}
return nil
}
validateDir := func(key string, value string, optional bool) error {
if err := checkConfigOverride(config.Metadata); err != nil {
return err
}
if !optional || value != "" {
if err := fsutil.EnsureDir(value); err != nil {
return err
}
}
return nil
}
existingDBPath := c.GetDatabasePath()
if input.DatabasePath != nil && existingDBPath != *input.DatabasePath {
if err := checkConfigOverride(config.Database); err != nil {
return makeConfigGeneralResult(), err
}
ext := filepath.Ext(*input.DatabasePath)
if ext != ".db" && ext != ".sqlite" && ext != ".sqlite3" {
return makeConfigGeneralResult(), fmt.Errorf("invalid database path, use extension db, sqlite, or sqlite3")
@@ -54,51 +84,72 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
c.Set(config.Database, input.DatabasePath)
}
if input.GeneratedPath != nil {
if err := utils.EnsureDir(*input.GeneratedPath); err != nil {
existingGeneratedPath := c.GetGeneratedPath()
if input.GeneratedPath != nil && existingGeneratedPath != *input.GeneratedPath {
if err := validateDir(config.Generated, *input.GeneratedPath, false); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.Generated, input.GeneratedPath)
}
if input.MetadataPath != nil {
if *input.MetadataPath != "" {
if err := utils.EnsureDir(*input.MetadataPath); err != nil {
return makeConfigGeneralResult(), err
}
}
c.Set(config.Metadata, input.MetadataPath)
}
if input.CachePath != nil {
if *input.CachePath != "" {
if err := utils.EnsureDir(*input.CachePath); err != nil {
return makeConfigGeneralResult(), err
}
}
c.Set(config.Cache, input.CachePath)
}
if !input.CalculateMd5 && input.VideoFileNamingAlgorithm == models.HashAlgorithmMd5 {
return makeConfigGeneralResult(), errors.New("calculateMD5 must be true if using MD5")
}
if input.VideoFileNamingAlgorithm != c.GetVideoFileNamingAlgorithm() {
// validate changing VideoFileNamingAlgorithm
if err := manager.ValidateVideoFileNamingAlgorithm(r.txnManager, input.VideoFileNamingAlgorithm); err != nil {
refreshScraperCache := false
existingScrapersPath := c.GetScrapersPath()
if input.ScrapersPath != nil && existingScrapersPath != *input.ScrapersPath {
if err := validateDir(config.ScrapersPath, *input.ScrapersPath, false); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.VideoFileNamingAlgorithm, input.VideoFileNamingAlgorithm)
refreshScraperCache = true
c.Set(config.ScrapersPath, input.ScrapersPath)
}
c.Set(config.CalculateMD5, input.CalculateMd5)
existingMetadataPath := c.GetMetadataPath()
if input.MetadataPath != nil && existingMetadataPath != *input.MetadataPath {
if err := validateDir(config.Metadata, *input.MetadataPath, true); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.Metadata, input.MetadataPath)
}
existingCachePath := c.GetCachePath()
if input.CachePath != nil && existingCachePath != *input.CachePath {
if err := validateDir(config.Cache, *input.CachePath, true); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.Cache, input.CachePath)
}
if input.VideoFileNamingAlgorithm != nil && *input.VideoFileNamingAlgorithm != c.GetVideoFileNamingAlgorithm() {
calculateMD5 := c.IsCalculateMD5()
if input.CalculateMd5 != nil {
calculateMD5 = *input.CalculateMd5
}
if !calculateMD5 && *input.VideoFileNamingAlgorithm == models.HashAlgorithmMd5 {
return makeConfigGeneralResult(), errors.New("calculateMD5 must be true if using MD5")
}
// validate changing VideoFileNamingAlgorithm
if err := manager.ValidateVideoFileNamingAlgorithm(r.txnManager, *input.VideoFileNamingAlgorithm); err != nil {
return makeConfigGeneralResult(), err
}
c.Set(config.VideoFileNamingAlgorithm, *input.VideoFileNamingAlgorithm)
}
if input.CalculateMd5 != nil {
c.Set(config.CalculateMD5, *input.CalculateMd5)
}
if input.ParallelTasks != nil {
c.Set(config.ParallelTasks, *input.ParallelTasks)
}
c.Set(config.PreviewAudio, input.PreviewAudio)
if input.PreviewAudio != nil {
c.Set(config.PreviewAudio, *input.PreviewAudio)
}
if input.PreviewSegments != nil {
c.Set(config.PreviewSegments, *input.PreviewSegments)
@@ -146,20 +197,22 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
c.Set(config.MaxSessionAge, *input.MaxSessionAge)
}
if input.TrustedProxies != nil {
c.Set(config.TrustedProxies, input.TrustedProxies)
}
if input.LogFile != nil {
c.Set(config.LogFile, input.LogFile)
}
c.Set(config.LogOut, input.LogOut)
c.Set(config.LogAccess, input.LogAccess)
if input.LogOut != nil {
c.Set(config.LogOut, *input.LogOut)
}
if input.LogLevel != c.GetLogLevel() {
if input.LogAccess != nil {
c.Set(config.LogAccess, *input.LogAccess)
}
if input.LogLevel != nil && *input.LogLevel != c.GetLogLevel() {
c.Set(config.LogLevel, input.LogLevel)
logger.SetLogLevel(input.LogLevel)
logger := manager.GetInstance().Logger
logger.SetLogLevel(*input.LogLevel)
}
if input.Excludes != nil {
@@ -182,14 +235,15 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
c.Set(config.GalleryExtensions, input.GalleryExtensions)
}
c.Set(config.CreateGalleriesFromFolders, input.CreateGalleriesFromFolders)
if input.CreateGalleriesFromFolders != nil {
c.Set(config.CreateGalleriesFromFolders, input.CreateGalleriesFromFolders)
}
if input.CustomPerformerImageLocation != nil {
c.Set(config.CustomPerformerImageLocation, *input.CustomPerformerImageLocation)
initialiseCustomImages()
}
refreshScraperCache := false
if input.ScraperUserAgent != nil {
c.Set(config.ScraperUserAgent, input.ScraperUserAgent)
refreshScraperCache = true
@@ -211,6 +265,10 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
c.Set(config.StashBoxes, input.StashBoxes)
}
if input.PythonPath != nil {
c.Set(config.PythonPath, input.PythonPath)
}
if err := c.Write(); err != nil {
return makeConfigGeneralResult(), err
}
@@ -225,17 +283,31 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) {
c := config.GetInstance()
setBool := func(key string, v *bool) {
if v != nil {
c.Set(key, *v)
}
}
setString := func(key string, v *string) {
if v != nil {
c.Set(key, *v)
}
}
if input.MenuItems != nil {
c.Set(config.MenuItems, input.MenuItems)
}
if input.SoundOnPreview != nil {
c.Set(config.SoundOnPreview, *input.SoundOnPreview)
}
setBool(config.SoundOnPreview, input.SoundOnPreview)
setBool(config.WallShowTitle, input.WallShowTitle)
if input.WallShowTitle != nil {
c.Set(config.WallShowTitle, *input.WallShowTitle)
}
setBool(config.NoBrowser, input.NoBrowser)
setBool(config.NotificationsEnabled, input.NotificationsEnabled)
setBool(config.ShowScrubber, input.ShowScrubber)
if input.WallPlayback != nil {
c.Set(config.WallPlayback, *input.WallPlayback)
@@ -245,32 +317,48 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
c.Set(config.MaximumLoopDuration, *input.MaximumLoopDuration)
}
if input.AutostartVideo != nil {
c.Set(config.AutostartVideo, *input.AutostartVideo)
}
if input.ShowStudioAsText != nil {
c.Set(config.ShowStudioAsText, *input.ShowStudioAsText)
}
setBool(config.AutostartVideo, input.AutostartVideo)
setBool(config.ShowStudioAsText, input.ShowStudioAsText)
setBool(config.AutostartVideoOnPlaySelected, input.AutostartVideoOnPlaySelected)
setBool(config.ContinuePlaylistDefault, input.ContinuePlaylistDefault)
if input.Language != nil {
c.Set(config.Language, *input.Language)
}
// deprecated field
if input.SlideshowDelay != nil {
c.Set(config.SlideshowDelay, *input.SlideshowDelay)
c.Set(config.ImageLightboxSlideshowDelay, *input.SlideshowDelay)
}
css := ""
if input.ImageLightbox != nil {
options := input.ImageLightbox
if options.SlideshowDelay != nil {
c.Set(config.ImageLightboxSlideshowDelay, *options.SlideshowDelay)
}
setString(config.ImageLightboxDisplayMode, (*string)(options.DisplayMode))
setBool(config.ImageLightboxScaleUp, options.ScaleUp)
setBool(config.ImageLightboxResetZoomOnNav, options.ResetZoomOnNav)
setString(config.ImageLightboxScrollMode, (*string)(options.ScrollMode))
if options.ScrollAttemptsBeforeChange != nil {
c.Set(config.ImageLightboxScrollAttemptsBeforeChange, *options.ScrollAttemptsBeforeChange)
}
}
if input.CSS != nil {
css = *input.CSS
c.SetCSS(*input.CSS)
}
c.SetCSS(css)
setBool(config.CSSEnabled, input.CSSEnabled)
if input.CSSEnabled != nil {
c.Set(config.CSSEnabled, *input.CSSEnabled)
if input.DisableDropdownCreate != nil {
ddc := input.DisableDropdownCreate
setBool(config.DisableDropdownCreatePerformer, ddc.Performer)
setBool(config.DisableDropdownCreateStudio, ddc.Studio)
setBool(config.DisableDropdownCreateTag, ddc.Tag)
}
if input.HandyKey != nil {
@@ -295,7 +383,9 @@ func (r *mutationResolver) ConfigureDlna(ctx context.Context, input models.Confi
c.Set(config.DLNAServerName, *input.ServerName)
}
c.Set(config.DLNADefaultIPWhitelist, input.WhitelistedIPs)
if input.WhitelistedIPs != nil {
c.Set(config.DLNADefaultIPWhitelist, input.WhitelistedIPs)
}
currentDLNAEnabled := c.GetDLNADefaultEnabled()
if input.Enabled != nil && *input.Enabled != currentDLNAEnabled {
@@ -312,7 +402,9 @@ func (r *mutationResolver) ConfigureDlna(ctx context.Context, input models.Confi
}
}
c.Set(config.DLNAInterfaces, input.Interfaces)
if input.Interfaces != nil {
c.Set(config.DLNAInterfaces, input.Interfaces)
}
if err := c.Write(); err != nil {
return makeConfigDLNAResult(), err
@@ -339,7 +431,10 @@ func (r *mutationResolver) ConfigureScraping(ctx context.Context, input models.C
c.Set(config.ScraperExcludeTagPatterns, input.ExcludeTagPatterns)
}
c.Set(config.ScraperCertCheck, input.ScraperCertCheck)
if input.ScraperCertCheck != nil {
c.Set(config.ScraperCertCheck, input.ScraperCertCheck)
}
if refreshScraperCache {
manager.GetInstance().RefreshScraperCache()
}
@@ -350,6 +445,40 @@ func (r *mutationResolver) ConfigureScraping(ctx context.Context, input models.C
return makeConfigScrapingResult(), nil
}
func (r *mutationResolver) ConfigureDefaults(ctx context.Context, input models.ConfigDefaultSettingsInput) (*models.ConfigDefaultSettingsResult, error) {
c := config.GetInstance()
if input.Identify != nil {
c.Set(config.DefaultIdentifySettings, input.Identify)
}
if input.Scan != nil {
c.Set(config.DefaultScanSettings, input.Scan)
}
if input.AutoTag != nil {
c.Set(config.DefaultAutoTagSettings, input.AutoTag)
}
if input.Generate != nil {
c.Set(config.DefaultGenerateSettings, input.Generate)
}
if input.DeleteFile != nil {
c.Set(config.DeleteFileDefault, *input.DeleteFile)
}
if input.DeleteGenerated != nil {
c.Set(config.DeleteGeneratedDefault, *input.DeleteGenerated)
}
if err := c.Write(); err != nil {
return makeConfigDefaultsResult(), err
}
return makeConfigDefaultsResult(), nil
}
func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input models.GenerateAPIKeyInput) (string, error) {
c := config.GetInstance()
@@ -372,3 +501,23 @@ func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input models.Gene
return newAPIKey, nil
}
func (r *mutationResolver) ConfigureUI(ctx context.Context, input map[string]interface{}) (map[string]interface{}, error) {
c := config.GetInstance()
c.SetUIConfiguration(input)
if err := c.Write(); err != nil {
return c.GetUIConfiguration(), err
}
return c.GetUIConfiguration(), nil
}
func (r *mutationResolver) ConfigureUISetting(ctx context.Context, key string, value interface{}) (map[string]interface{}, error) {
c := config.GetInstance()
cfg := c.GetUIConfiguration()
cfg[key] = value
return r.ConfigureUI(ctx, cfg)
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
)

View File

@@ -5,12 +5,18 @@ import (
"database/sql"
"errors"
"fmt"
"os"
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@@ -32,7 +38,7 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.Galle
}
// for manually created galleries, generate checksum from title
checksum := utils.MD5FromString(input.Title)
checksum := md5.FromString(input.Title)
// Populate a new performer from the input
currentTime := time.Now()
@@ -107,7 +113,7 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.Galle
}
func (r *mutationResolver) updateGalleryPerformers(qb models.GalleryReaderWriter, galleryID int, performerIDs []string) error {
ids, err := utils.StringSliceToIntSlice(performerIDs)
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
if err != nil {
return err
}
@@ -115,7 +121,7 @@ func (r *mutationResolver) updateGalleryPerformers(qb models.GalleryReaderWriter
}
func (r *mutationResolver) updateGalleryTags(qb models.GalleryReaderWriter, galleryID int, tagIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagIDs)
ids, err := stringslice.StringSliceToIntSlice(tagIDs)
if err != nil {
return err
}
@@ -123,7 +129,7 @@ func (r *mutationResolver) updateGalleryTags(qb models.GalleryReaderWriter, gall
}
func (r *mutationResolver) updateGalleryScenes(qb models.GalleryReaderWriter, galleryID int, sceneIDs []string) error {
ids, err := utils.StringSliceToIntSlice(sceneIDs)
ids, err := stringslice.StringSliceToIntSlice(sceneIDs)
if err != nil {
return err
}
@@ -222,7 +228,7 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, transl
// if gallery is not zip-based, then generate the checksum from the title
if !originalGallery.Path.Valid {
checksum := utils.MD5FromString(*input.Title)
checksum := md5.FromString(*input.Title)
updatedGallery.Checksum = &checksum
}
@@ -389,14 +395,20 @@ func adjustGallerySceneIDs(qb models.GalleryReader, galleryID int, ids models.Bu
}
func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) {
galleryIDs, err := utils.StringSliceToIntSlice(input.Ids)
galleryIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return false, err
}
var galleries []*models.Gallery
var imgsToPostProcess []*models.Image
var imgsToDelete []*models.Image
var imgsDestroyed []*models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
deleteFile := utils.IsTrue(input.DeleteFile)
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Gallery()
@@ -422,13 +434,19 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
}
for _, img := range imgs {
if err := iqb.Destroy(img.ID); err != nil {
if err := image.Destroy(img, iqb, fileDeleter, deleteGenerated, false); err != nil {
return err
}
imgsToPostProcess = append(imgsToPostProcess, img)
imgsDestroyed = append(imgsDestroyed, img)
}
} else if input.DeleteFile != nil && *input.DeleteFile {
if deleteFile {
if err := fileDeleter.Files([]string{gallery.Path.String}); err != nil {
return err
}
}
} else if deleteFile {
// Delete image if it is only attached to this gallery
imgs, err := iqb.FindByGalleryID(id)
if err != nil {
@@ -441,15 +459,17 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
return err
}
if len(imgGalleries) == 0 {
if err := iqb.Destroy(img.ID); err != nil {
if len(imgGalleries) == 1 {
if err := image.Destroy(img, iqb, fileDeleter, deleteGenerated, deleteFile); err != nil {
return err
}
imgsToDelete = append(imgsToDelete, img)
imgsToPostProcess = append(imgsToPostProcess, img)
imgsDestroyed = append(imgsDestroyed, img)
}
}
// we only want to delete a folder-based gallery if it is empty.
// don't do this with the file deleter
}
if err := qb.Destroy(id); err != nil {
@@ -459,49 +479,60 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
for _, gallery := range galleries {
manager.DeleteGalleryFile(gallery)
}
// perform the post-commit actions
fileDeleter.Commit()
for _, img := range imgsToDelete {
manager.DeleteImageFile(img)
}
}
// if delete generated is true, then delete the generated files
// for the gallery
if input.DeleteGenerated != nil && *input.DeleteGenerated {
for _, img := range imgsToPostProcess {
manager.DeleteGeneratedImageFiles(img)
for _, gallery := range galleries {
// don't delete stash library paths
if utils.IsTrue(input.DeleteFile) && !gallery.Zip && gallery.Path.Valid && !isStashPath(gallery.Path.String) {
// try to remove the folder - it is possible that it is not empty
// so swallow the error if present
_ = os.Remove(gallery.Path.String)
}
}
// call post hook after performing the other actions
for _, gallery := range galleries {
r.hookExecutor.ExecutePostHooks(ctx, gallery.ID, plugin.GalleryDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, gallery.ID, plugin.GalleryDestroyPost, plugin.GalleryDestroyInput{
GalleryDestroyInput: input,
Checksum: gallery.Checksum,
Path: gallery.Path.String,
}, nil)
}
// call image destroy post hook as well
for _, img := range imgsToDelete {
r.hookExecutor.ExecutePostHooks(ctx, img.ID, plugin.ImageDestroyPost, nil, nil)
for _, img := range imgsDestroyed {
r.hookExecutor.ExecutePostHooks(ctx, img.ID, plugin.ImageDestroyPost, plugin.ImageDestroyInput{
Checksum: img.Checksum,
Path: img.Path,
}, nil)
}
return true, nil
}
func isStashPath(path string) bool {
stashConfigs := manager.GetInstance().Config.GetStashPaths()
for _, config := range stashConfigs {
if path == config.Path {
return true
}
}
return false
}
func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.GalleryAddInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil {
return false, err
}
imageIDs, err := utils.StringSliceToIntSlice(input.ImageIds)
imageIDs, err := stringslice.StringSliceToIntSlice(input.ImageIds)
if err != nil {
return false, err
}
@@ -526,7 +557,7 @@ func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.Ga
return err
}
newIDs = utils.IntAppendUniques(newIDs, imageIDs)
newIDs = intslice.IntAppendUniques(newIDs, imageIDs)
return qb.UpdateImages(galleryID, newIDs)
}); err != nil {
return false, err
@@ -541,7 +572,7 @@ func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input models
return false, err
}
imageIDs, err := utils.StringSliceToIntSlice(input.ImageIds)
imageIDs, err := stringslice.StringSliceToIntSlice(input.ImageIds)
if err != nil {
return false, err
}
@@ -566,7 +597,7 @@ func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input models
return err
}
newIDs = utils.IntExclude(newIDs, imageIDs)
newIDs = intslice.IntExclude(newIDs, imageIDs)
return qb.UpdateImages(galleryID, newIDs)
}); err != nil {
return false, err

View File

@@ -6,9 +6,12 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@@ -131,7 +134,7 @@ func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator
}
func (r *mutationResolver) updateImageGalleries(qb models.ImageReaderWriter, imageID int, galleryIDs []string) error {
ids, err := utils.StringSliceToIntSlice(galleryIDs)
ids, err := stringslice.StringSliceToIntSlice(galleryIDs)
if err != nil {
return err
}
@@ -139,7 +142,7 @@ func (r *mutationResolver) updateImageGalleries(qb models.ImageReaderWriter, ima
}
func (r *mutationResolver) updateImagePerformers(qb models.ImageReaderWriter, imageID int, performerIDs []string) error {
ids, err := utils.StringSliceToIntSlice(performerIDs)
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
if err != nil {
return err
}
@@ -147,7 +150,7 @@ func (r *mutationResolver) updateImagePerformers(qb models.ImageReaderWriter, im
}
func (r *mutationResolver) updateImageTags(qb models.ImageReaderWriter, imageID int, tagsIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagsIDs)
ids, err := stringslice.StringSliceToIntSlice(tagsIDs)
if err != nil {
return err
}
@@ -155,7 +158,7 @@ func (r *mutationResolver) updateImageTags(qb models.ImageReaderWriter, imageID
}
func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.BulkImageUpdateInput) (ret []*models.Image, err error) {
imageIDs, err := utils.StringSliceToIntSlice(input.Ids)
imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
@@ -281,89 +284,90 @@ func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageD
return false, err
}
var image *models.Image
var i *models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
image, err = qb.Find(imageID)
i, err = qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
if i == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
return qb.Destroy(imageID)
return image.Destroy(i, qb, fileDeleter, utils.IsTrue(input.DeleteGenerated), utils.IsTrue(input.DeleteFile))
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
// perform the post-commit actions
fileDeleter.Commit()
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, i.ID, plugin.ImageDestroyPost, plugin.ImageDestroyInput{
ImageDestroyInput: input,
Checksum: i.Checksum,
Path: i.Path,
}, nil)
return true, nil
}
func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.ImagesDestroyInput) (ret bool, err error) {
imageIDs, err := utils.StringSliceToIntSlice(input.Ids)
imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return false, err
}
var images []*models.Image
fileDeleter := &image.FileDeleter{
Deleter: *file.NewDeleter(),
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Image()
for _, imageID := range imageIDs {
image, err := qb.Find(imageID)
i, err := qb.Find(imageID)
if err != nil {
return err
}
if image == nil {
if i == nil {
return fmt.Errorf("image with id %d not found", imageID)
}
images = append(images, image)
if err := qb.Destroy(imageID); err != nil {
images = append(images, i)
if err := image.Destroy(i, qb, fileDeleter, utils.IsTrue(input.DeleteGenerated), utils.IsTrue(input.DeleteFile)); err != nil {
return err
}
}
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// perform the post-commit actions
fileDeleter.Commit()
for _, image := range images {
// if delete generated is true, then delete the generated files
// for the image
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedImageFiles(image)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteImageFile(image)
}
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, image.ID, plugin.ImageDestroyPost, plugin.ImagesDestroyInput{
ImagesDestroyInput: input,
Checksum: image.Checksum,
Path: image.Path,
}, nil)
}
return true, nil

View File

@@ -4,7 +4,7 @@ import (
"context"
"strconv"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
)
func (r *mutationResolver) StopJob(ctx context.Context, jobID string) (bool, error) {

View File

@@ -9,12 +9,12 @@ import (
"sync"
"time"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) {
@@ -61,7 +61,7 @@ func (r *mutationResolver) ExportObjects(ctx context.Context, input models.Expor
var wg sync.WaitGroup
wg.Add(1)
t.Start(&wg)
t.Start(ctx, &wg)
if t.DownloadHash != "" {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
@@ -90,6 +90,13 @@ func (r *mutationResolver) MetadataAutoTag(ctx context.Context, input models.Aut
return strconv.Itoa(jobID), nil
}
func (r *mutationResolver) MetadataIdentify(ctx context.Context, input models.IdentifyMetadataInput) (string, error) {
t := manager.CreateIdentifyJob(input)
jobID := manager.GetInstance().JobManager.Add(ctx, "Identifying...", t)
return strconv.Itoa(jobID), nil
}
func (r *mutationResolver) MetadataClean(ctx context.Context, input models.CleanMetadataInput) (string, error) {
jobID := manager.GetInstance().Clean(ctx, input)
return strconv.Itoa(jobID), nil
@@ -106,7 +113,7 @@ func (r *mutationResolver) BackupDatabase(ctx context.Context, input models.Back
mgr := manager.GetInstance()
var backupPath string
if download {
if err := utils.EnsureDir(mgr.Paths.Generated.Downloads); err != nil {
if err := fsutil.EnsureDir(mgr.Paths.Generated.Downloads); err != nil {
return nil, fmt.Errorf("could not create backup directory %v: %w", mgr.Paths.Generated.Downloads, err)
}
f, err := os.CreateTemp(mgr.Paths.Generated.Downloads, "backup*.sqlite")
@@ -126,7 +133,10 @@ func (r *mutationResolver) BackupDatabase(ctx context.Context, input models.Back
}
if download {
downloadHash := mgr.DownloadStore.RegisterFile(backupPath, "", false)
downloadHash, err := mgr.DownloadStore.RegisterFile(backupPath, "", false)
if err != nil {
return nil, fmt.Errorf("error registering file for download: %w", err)
}
logger.Debugf("Generated backup file %s with hash %s", backupPath, downloadHash)
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)

View File

@@ -3,11 +3,14 @@ package api
import (
"context"
"database/sql"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@@ -24,7 +27,7 @@ func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Mo
func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCreateInput) (*models.Movie, error) {
// generate checksum from movie name rather than image
checksum := utils.MD5FromString(input.Name)
checksum := md5.FromString(input.Name)
var frontimageData []byte
var backimageData []byte
@@ -38,7 +41,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
// Process the base 64 encoded image string
if input.FrontImage != nil {
frontimageData, err = utils.ProcessImageInput(*input.FrontImage)
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
if err != nil {
return nil, err
}
@@ -46,7 +49,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
// Process the base 64 encoded image string
if input.BackImage != nil {
backimageData, err = utils.ProcessImageInput(*input.BackImage)
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
if err != nil {
return nil, err
}
@@ -139,7 +142,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
var frontimageData []byte
frontImageIncluded := translator.hasField("front_image")
if input.FrontImage != nil {
frontimageData, err = utils.ProcessImageInput(*input.FrontImage)
frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
if err != nil {
return nil, err
}
@@ -147,7 +150,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
backImageIncluded := translator.hasField("back_image")
var backimageData []byte
if input.BackImage != nil {
backimageData, err = utils.ProcessImageInput(*input.BackImage)
backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
if err != nil {
return nil, err
}
@@ -155,7 +158,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
if input.Name != nil {
// generate checksum from movie name rather than image
checksum := utils.MD5FromString(*input.Name)
checksum := md5.FromString(*input.Name)
updatedMovie.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedMovie.Checksum = &checksum
}
@@ -202,7 +205,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
// HACK - if front image is null and back image is not null, then set the front image
// to the default image since we can't have a null front image and a non-null back image
if frontimageData == nil && backimageData != nil {
frontimageData, _ = utils.ProcessImageInput(models.DefaultMovieImage)
frontimageData, _ = utils.ProcessImageInput(ctx, models.DefaultMovieImage)
}
if err := qb.UpdateImages(movie.ID, frontimageData, backimageData); err != nil {
@@ -220,6 +223,71 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
return r.getMovie(ctx, movie.ID)
}
func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input models.BulkMovieUpdateInput) ([]*models.Movie, error) {
movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
updatedTime := time.Now()
translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx),
}
updatedMovie := models.MoviePartial{
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
}
updatedMovie.Rating = translator.nullInt64(input.Rating, "rating")
updatedMovie.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
updatedMovie.Director = translator.nullString(input.Director, "director")
ret := []*models.Movie{}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Movie()
for _, movieID := range movieIDs {
updatedMovie.ID = movieID
existing, err := qb.Find(movieID)
if err != nil {
return err
}
if existing == nil {
return fmt.Errorf("movie with id %d not found", movieID)
}
movie, err := qb.Update(updatedMovie)
if err != nil {
return err
}
ret = append(ret, movie)
}
return nil
}); err != nil {
return nil, err
}
var newRet []*models.Movie
for _, movie := range ret {
r.hookExecutor.ExecutePostHooks(ctx, movie.ID, plugin.MovieUpdatePost, input, translator.getFields())
movie, err = r.getMovie(ctx, movie.ID)
if err != nil {
return nil, err
}
newRet = append(newRet, movie)
}
return newRet, nil
}
func (r *mutationResolver) MovieDestroy(ctx context.Context, input models.MovieDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID)
if err != nil {
@@ -238,7 +306,7 @@ func (r *mutationResolver) MovieDestroy(ctx context.Context, input models.MovieD
}
func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(movieIDs)
ids, err := stringslice.StringSliceToIntSlice(movieIDs)
if err != nil {
return false, err
}

View File

@@ -7,9 +7,11 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@@ -26,13 +28,13 @@ func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *model
func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) {
// generate checksum from performer name rather than image
checksum := utils.MD5FromString(input.Name)
checksum := md5.FromString(input.Name)
var imageData []byte
var err error
if input.Image != nil {
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
}
if err != nil {
@@ -115,6 +117,9 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
weight := int64(*input.Weight)
newPerformer.Weight = sql.NullInt64{Int64: weight, Valid: true}
}
if input.IgnoreAutoTag != nil {
newPerformer.IgnoreAutoTag = *input.IgnoreAutoTag
}
if err := performer.ValidateDeathDate(nil, input.Birthdate, input.DeathDate); err != nil {
if err != nil {
@@ -178,7 +183,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
var err error
imageIncluded := translator.hasField("image")
if input.Image != nil {
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
@@ -186,7 +191,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
if input.Name != nil {
// generate checksum from performer name rather than image
checksum := utils.MD5FromString(*input.Name)
checksum := md5.FromString(*input.Name)
updatedPerformer.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedPerformer.Checksum = &checksum
@@ -221,6 +226,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
// Start the transaction and save the p
var p *models.Performer
@@ -285,7 +291,7 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per
}
func (r *mutationResolver) updatePerformerTags(qb models.PerformerReaderWriter, performerID int, tagsIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagsIDs)
ids, err := stringslice.StringSliceToIntSlice(tagsIDs)
if err != nil {
return err
}
@@ -293,7 +299,7 @@ func (r *mutationResolver) updatePerformerTags(qb models.PerformerReaderWriter,
}
func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models.BulkPerformerUpdateInput) ([]*models.Performer, error) {
performerIDs, err := utils.StringSliceToIntSlice(input.Ids)
performerIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
@@ -329,6 +335,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models
updatedPerformer.DeathDate = translator.sqliteDate(input.DeathDate, "death_date")
updatedPerformer.HairColor = translator.nullString(input.HairColor, "hair_color")
updatedPerformer.Weight = translator.nullInt64(input.Weight, "weight")
updatedPerformer.IgnoreAutoTag = input.IgnoreAutoTag
if translator.hasField("gender") {
if input.Gender != nil {
@@ -420,7 +427,7 @@ func (r *mutationResolver) PerformerDestroy(ctx context.Context, input models.Pe
}
func (r *mutationResolver) PerformersDestroy(ctx context.Context, performerIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(performerIDs)
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
if err != nil {
return false, err
}

View File

@@ -3,8 +3,8 @@ package api
import (
"context"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/models"
)

View File

@@ -7,10 +7,14 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@@ -32,7 +36,7 @@ func (r *mutationResolver) SceneUpdate(ctx context.Context, input models.SceneUp
// Start the transaction and save the scene
if err := r.withTxn(ctx, func(repo models.Repository) error {
ret, err = r.sceneUpdate(input, translator, repo)
ret, err = r.sceneUpdate(ctx, input, translator, repo)
return err
}); err != nil {
return nil, err
@@ -52,7 +56,7 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce
inputMap: inputMaps[i],
}
thisScene, err := r.sceneUpdate(*scene, translator, repo)
thisScene, err := r.sceneUpdate(ctx, *scene, translator, repo)
ret = append(ret, thisScene)
if err != nil {
@@ -85,7 +89,7 @@ func (r *mutationResolver) ScenesUpdate(ctx context.Context, input []*models.Sce
return newRet, nil
}
func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Scene, error) {
func (r *mutationResolver) sceneUpdate(ctx context.Context, input models.SceneUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Scene, error) {
// Populate scene from the input
sceneID, err := strconv.Atoi(input.ID)
if err != nil {
@@ -110,7 +114,7 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator
if input.CoverImage != nil && *input.CoverImage != "" {
var err error
coverImageData, err = utils.ProcessImageInput(*input.CoverImage)
coverImageData, err = utils.ProcessImageInput(ctx, *input.CoverImage)
if err != nil {
return nil, err
}
@@ -119,7 +123,7 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator
}
qb := repo.Scene()
scene, err := qb.Update(updatedScene)
s, err := qb.Update(updatedScene)
if err != nil {
return nil, err
}
@@ -169,17 +173,17 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator
// only update the cover image if provided and everything else was successful
if coverImageData != nil {
err = manager.SetSceneScreenshot(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), coverImageData)
err = scene.SetScreenshot(manager.GetInstance().Paths, s.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), coverImageData)
if err != nil {
return nil, err
}
}
return scene, nil
return s, nil
}
func (r *mutationResolver) updateScenePerformers(qb models.SceneReaderWriter, sceneID int, performerIDs []string) error {
ids, err := utils.StringSliceToIntSlice(performerIDs)
ids, err := stringslice.StringSliceToIntSlice(performerIDs)
if err != nil {
return err
}
@@ -213,7 +217,7 @@ func (r *mutationResolver) updateSceneMovies(qb models.SceneReaderWriter, sceneI
}
func (r *mutationResolver) updateSceneTags(qb models.SceneReaderWriter, sceneID int, tagsIDs []string) error {
ids, err := utils.StringSliceToIntSlice(tagsIDs)
ids, err := stringslice.StringSliceToIntSlice(tagsIDs)
if err != nil {
return err
}
@@ -221,7 +225,7 @@ func (r *mutationResolver) updateSceneTags(qb models.SceneReaderWriter, sceneID
}
func (r *mutationResolver) updateSceneGalleries(qb models.SceneReaderWriter, sceneID int, galleryIDs []string) error {
ids, err := utils.StringSliceToIntSlice(galleryIDs)
ids, err := stringslice.StringSliceToIntSlice(galleryIDs)
if err != nil {
return err
}
@@ -229,7 +233,7 @@ func (r *mutationResolver) updateSceneGalleries(qb models.SceneReaderWriter, sce
}
func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.BulkSceneUpdateInput) ([]*models.Scene, error) {
sceneIDs, err := utils.StringSliceToIntSlice(input.Ids)
sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil {
return nil, err
}
@@ -455,96 +459,105 @@ func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneD
return false, err
}
var scene *models.Scene
var postCommitFunc func()
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
var s *models.Scene
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
deleteFile := utils.IsTrue(input.DeleteFile)
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Scene()
var err error
scene, err = qb.Find(sceneID)
s, err = qb.Find(sceneID)
if err != nil {
return err
}
if scene == nil {
if s == nil {
return fmt.Errorf("scene with id %d not found", sceneID)
}
postCommitFunc, err = manager.DestroyScene(scene, repo)
return err
// kill any running encoders
manager.KillRunningStreams(s, fileNamingAlgo)
return scene.Destroy(s, repo, fileDeleter, deleteGenerated, deleteFile)
}); err != nil {
fileDeleter.Rollback()
return false, err
}
// perform the post-commit actions
postCommitFunc()
// if delete generated is true, then delete the generated files
// for the scene
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedSceneFiles(scene, config.GetInstance().GetVideoFileNamingAlgorithm())
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteSceneFile(scene)
}
fileDeleter.Commit()
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, plugin.SceneDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, s.ID, plugin.SceneDestroyPost, plugin.SceneDestroyInput{
SceneDestroyInput: input,
Checksum: s.Checksum.String,
OSHash: s.OSHash.String,
Path: s.Path,
}, nil)
return true, nil
}
func (r *mutationResolver) ScenesDestroy(ctx context.Context, input models.ScenesDestroyInput) (bool, error) {
var scenes []*models.Scene
var postCommitFuncs []func()
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
deleteGenerated := utils.IsTrue(input.DeleteGenerated)
deleteFile := utils.IsTrue(input.DeleteFile)
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Scene()
for _, id := range input.Ids {
sceneID, _ := strconv.Atoi(id)
scene, err := qb.Find(sceneID)
s, err := qb.Find(sceneID)
if err != nil {
return err
}
if scene != nil {
scenes = append(scenes, scene)
}
f, err := manager.DestroyScene(scene, repo)
if err != nil {
return err
if s != nil {
scenes = append(scenes, s)
}
postCommitFuncs = append(postCommitFuncs, f)
// kill any running encoders
manager.KillRunningStreams(s, fileNamingAlgo)
if err := scene.Destroy(s, repo, fileDeleter, deleteGenerated, deleteFile); err != nil {
return err
}
}
return nil
}); err != nil {
fileDeleter.Rollback()
return false, err
}
for _, f := range postCommitFuncs {
f()
}
// perform the post-commit actions
fileDeleter.Commit()
fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm()
for _, scene := range scenes {
// if delete generated is true, then delete the generated files
// for the scene
if input.DeleteGenerated != nil && *input.DeleteGenerated {
manager.DeleteGeneratedSceneFiles(scene, fileNamingAlgo)
}
// if delete file is true, then delete the file as well
// if it fails, just log a message
if input.DeleteFile != nil && *input.DeleteFile {
manager.DeleteSceneFile(scene)
}
// call post hook after performing the other actions
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, plugin.SceneDestroyPost, input, nil)
r.hookExecutor.ExecutePostHooks(ctx, scene.ID, plugin.SceneDestroyPost, plugin.ScenesDestroyInput{
ScenesDestroyInput: input,
Checksum: scene.Checksum.String,
OSHash: scene.OSHash.String,
Path: scene.Path,
}, nil)
}
return true, nil
@@ -582,7 +595,7 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input models.S
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}
tagIDs, err := utils.StringSliceToIntSlice(input.TagIds)
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
if err != nil {
return nil, err
}
@@ -622,7 +635,7 @@ func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input models.S
UpdatedAt: models.SQLiteTimestamp{Timestamp: time.Now()},
}
tagIDs, err := utils.StringSliceToIntSlice(input.TagIds)
tagIDs, err := stringslice.StringSliceToIntSlice(input.TagIds)
if err != nil {
return nil, err
}
@@ -645,7 +658,14 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
return false, err
}
var postCommitFunc func()
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.SceneMarker()
sqb := repo.Scene()
@@ -660,18 +680,19 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
return fmt.Errorf("scene marker with id %d not found", markerID)
}
scene, err := sqb.Find(int(marker.SceneID.Int64))
s, err := sqb.Find(int(marker.SceneID.Int64))
if err != nil {
return err
}
postCommitFunc, err = manager.DestroySceneMarker(scene, marker, qb)
return err
return scene.DestroyMarker(s, marker, qb, fileDeleter)
}); err != nil {
fileDeleter.Rollback()
return false, err
}
postCommitFunc()
// perform the post-commit actions
fileDeleter.Commit()
r.hookExecutor.ExecutePostHooks(ctx, markerID, plugin.SceneMarkerDestroyPost, id, nil)
@@ -681,7 +702,15 @@ func (r *mutationResolver) SceneMarkerDestroy(ctx context.Context, id string) (b
func (r *mutationResolver) changeMarker(ctx context.Context, changeType int, changedMarker models.SceneMarker, tagIDs []int) (*models.SceneMarker, error) {
var existingMarker *models.SceneMarker
var sceneMarker *models.SceneMarker
var scene *models.Scene
var s *models.Scene
fileNamingAlgo := manager.GetInstance().Config.GetVideoFileNamingAlgorithm()
fileDeleter := &scene.FileDeleter{
Deleter: *file.NewDeleter(),
FileNamingAlgo: fileNamingAlgo,
Paths: manager.GetInstance().Paths,
}
// Start the transaction and save the scene marker
if err := r.withTxn(ctx, func(repo models.Repository) error {
@@ -703,26 +732,31 @@ func (r *mutationResolver) changeMarker(ctx context.Context, changeType int, cha
return err
}
scene, err = sqb.Find(int(existingMarker.SceneID.Int64))
s, err = sqb.Find(int(existingMarker.SceneID.Int64))
}
if err != nil {
return err
}
// remove the marker preview if the timestamp was changed
if s != nil && existingMarker != nil && existingMarker.Seconds != changedMarker.Seconds {
seconds := int(existingMarker.Seconds)
if err := fileDeleter.MarkMarkerFiles(s, seconds); err != nil {
return err
}
}
// Save the marker tags
// If this tag is the primary tag, then let's not add it.
tagIDs = utils.IntExclude(tagIDs, []int{changedMarker.PrimaryTagID})
tagIDs = intslice.IntExclude(tagIDs, []int{changedMarker.PrimaryTagID})
return qb.UpdateTags(sceneMarker.ID, tagIDs)
}); err != nil {
fileDeleter.Rollback()
return nil, err
}
// remove the marker preview if the timestamp was changed
if scene != nil && existingMarker != nil && existingMarker.Seconds != changedMarker.Seconds {
seconds := int(existingMarker.Seconds)
manager.DeleteSceneMarkerFiles(scene, seconds, config.GetInstance().GetVideoFileNamingAlgorithm())
}
// perform the post-commit actions
fileDeleter.Commit()
return sceneMarker, nil
}

View File

@@ -3,7 +3,7 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
)
func (r *mutationResolver) ReloadScrapers(ctx context.Context) (bool, error) {

View File

@@ -0,0 +1,88 @@
package api
import (
"context"
"fmt"
"strconv"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scraper/stashbox"
)
func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input models.StashBoxFingerprintSubmissionInput) (bool, error) {
boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
return false, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager)
return client.SubmitStashBoxFingerprints(ctx, input.SceneIds, boxes[input.StashBoxIndex].Endpoint)
}
func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input models.StashBoxBatchPerformerTagInput) (string, error) {
jobID := manager.GetInstance().StashBoxBatchPerformerTag(ctx, input)
return strconv.Itoa(jobID), nil
}
func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input models.StashBoxDraftSubmissionInput) (*string, error) {
boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager)
id, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
var res *string
err = r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
qb := repo.Scene()
scene, err := qb.Find(id)
if err != nil {
return err
}
filepath := manager.GetInstance().Paths.Scene.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()))
res, err = client.SubmitSceneDraft(ctx, id, boxes[input.StashBoxIndex].Endpoint, filepath)
return err
})
return res, err
}
func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, input models.StashBoxDraftSubmissionInput) (*string, error) {
boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex)
}
client := stashbox.NewClient(*boxes[input.StashBoxIndex], r.txnManager)
id, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
var res *string
err = r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
qb := repo.Performer()
performer, err := qb.Find(id)
if err != nil {
return err
}
res, err = client.SubmitPerformerDraft(ctx, performer, boxes[input.StashBoxIndex].Endpoint)
return err
})
return res, err
}

View File

@@ -3,11 +3,14 @@ package api
import (
"context"
"database/sql"
"github.com/stashapp/stash/pkg/studio"
"strconv"
"time"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/studio"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/utils"
@@ -26,14 +29,14 @@ func (r *mutationResolver) getStudio(ctx context.Context, id int) (ret *models.S
func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) {
// generate checksum from studio name rather than image
checksum := utils.MD5FromString(input.Name)
checksum := md5.FromString(input.Name)
var imageData []byte
var err error
// Process the base 64 encoded image string
if input.Image != nil {
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
@@ -63,6 +66,9 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
if input.Details != nil {
newStudio.Details = sql.NullString{String: *input.Details, Valid: true}
}
if input.IgnoreAutoTag != nil {
newStudio.IgnoreAutoTag = *input.IgnoreAutoTag
}
// Start the transaction and save the studio
var s *models.Studio
@@ -129,14 +135,14 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
imageIncluded := translator.hasField("image")
if input.Image != nil {
var err error
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
}
if input.Name != nil {
// generate checksum from studio name rather than image
checksum := utils.MD5FromString(*input.Name)
checksum := md5.FromString(*input.Name)
updatedStudio.Name = &sql.NullString{String: *input.Name, Valid: true}
updatedStudio.Checksum = &checksum
}
@@ -145,6 +151,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
updatedStudio.Details = translator.nullString(input.Details, "details")
updatedStudio.ParentID = translator.nullInt64FromString(input.ParentID, "parent_id")
updatedStudio.Rating = translator.nullInt64(input.Rating, "rating")
updatedStudio.IgnoreAutoTag = input.IgnoreAutoTag
// Start the transaction and save the studio
var s *models.Studio
@@ -218,7 +225,7 @@ func (r *mutationResolver) StudioDestroy(ctx context.Context, input models.Studi
}
func (r *mutationResolver) StudiosDestroy(ctx context.Context, studioIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(studioIDs)
ids, err := stringslice.StringSliceToIntSlice(studioIDs)
if err != nil {
return false, err
}

View File

@@ -6,8 +6,10 @@ import (
"strconv"
"time"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/tag"
"github.com/stashapp/stash/pkg/utils"
)
@@ -32,18 +34,39 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreate
UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime},
}
if input.IgnoreAutoTag != nil {
newTag.IgnoreAutoTag = *input.IgnoreAutoTag
}
var imageData []byte
var err error
if input.Image != nil {
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
}
// Start the transaction and save the t
var parentIDs []int
var childIDs []int
if len(input.ParentIds) > 0 {
parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds)
if err != nil {
return nil, err
}
}
if len(input.ChildIds) > 0 {
childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds)
if err != nil {
return nil, err
}
}
// Start the transaction and save the tag
var t *models.Tag
if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.Tag()
@@ -75,24 +98,22 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreate
}
}
if input.ParentIds != nil && len(input.ParentIds) > 0 {
ids, err := utils.StringSliceToIntSlice(input.ParentIds)
if err != nil {
return err
}
if err := qb.UpdateParentTags(t.ID, ids); err != nil {
if len(parentIDs) > 0 {
if err := qb.UpdateParentTags(t.ID, parentIDs); err != nil {
return err
}
}
if input.ChildIds != nil && len(input.ChildIds) > 0 {
ids, err := utils.StringSliceToIntSlice(input.ChildIds)
if err != nil {
if len(childIDs) > 0 {
if err := qb.UpdateChildTags(t.ID, childIDs); err != nil {
return err
}
}
if err := qb.UpdateChildTags(t.ID, ids); err != nil {
// FIXME: This should be called before any changes are made, but
// requires a rewrite of ValidateHierarchy.
if len(parentIDs) > 0 || len(childIDs) > 0 {
if err := tag.ValidateHierarchy(t, parentIDs, childIDs, qb); err != nil {
return err
}
}
@@ -121,13 +142,30 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
imageIncluded := translator.hasField("image")
if input.Image != nil {
imageData, err = utils.ProcessImageInput(*input.Image)
imageData, err = utils.ProcessImageInput(ctx, *input.Image)
if err != nil {
return nil, err
}
}
var parentIDs []int
var childIDs []int
if translator.hasField("parent_ids") {
parentIDs, err = stringslice.StringSliceToIntSlice(input.ParentIds)
if err != nil {
return nil, err
}
}
if translator.hasField("child_ids") {
childIDs, err = stringslice.StringSliceToIntSlice(input.ChildIds)
if err != nil {
return nil, err
}
}
// Start the transaction and save the tag
var t *models.Tag
if err := r.withTxn(ctx, func(repo models.Repository) error {
@@ -144,8 +182,9 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
}
updatedTag := models.TagPartial{
ID: tagID,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
ID: tagID,
IgnoreAutoTag: input.IgnoreAutoTag,
UpdatedAt: &models.SQLiteTimestamp{Timestamp: time.Now()},
}
if input.Name != nil && t.Name != *input.Name {
@@ -183,29 +222,6 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
}
}
var parentIDs []int
var childIDs []int
if translator.hasField("parent_ids") {
parentIDs, err = utils.StringSliceToIntSlice(input.ParentIds)
if err != nil {
return err
}
}
if translator.hasField("child_ids") {
childIDs, err = utils.StringSliceToIntSlice(input.ChildIds)
if err != nil {
return err
}
}
if parentIDs != nil || childIDs != nil {
if err := tag.EnsureUniqueHierarchy(tagID, parentIDs, childIDs, qb); err != nil {
return err
}
}
if parentIDs != nil {
if err := qb.UpdateParentTags(tagID, parentIDs); err != nil {
return err
@@ -218,6 +234,15 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
}
}
// FIXME: This should be called before any changes are made, but
// requires a rewrite of ValidateHierarchy.
if parentIDs != nil || childIDs != nil {
if err := tag.ValidateHierarchy(t, parentIDs, childIDs, qb); err != nil {
logger.Errorf("Error saving tag: %s", err)
return err
}
}
return nil
}); err != nil {
return nil, err
@@ -245,7 +270,7 @@ func (r *mutationResolver) TagDestroy(ctx context.Context, input models.TagDestr
}
func (r *mutationResolver) TagsDestroy(ctx context.Context, tagIDs []string) (bool, error) {
ids, err := utils.StringSliceToIntSlice(tagIDs)
ids, err := stringslice.StringSliceToIntSlice(tagIDs)
if err != nil {
return false, err
}
@@ -271,7 +296,7 @@ func (r *mutationResolver) TagsDestroy(ctx context.Context, tagIDs []string) (bo
}
func (r *mutationResolver) TagsMerge(ctx context.Context, input models.TagsMergeInput) (*models.Tag, error) {
source, err := utils.StringSliceToIntSlice(input.Source)
source, err := stringslice.StringSliceToIntSlice(input.Source)
if err != nil {
return nil, err
}
@@ -317,10 +342,17 @@ func (r *mutationResolver) TagsMerge(ctx context.Context, input models.TagsMerge
return err
}
err = tag.ValidateHierarchy(t, parents, children, qb)
if err != nil {
logger.Errorf("Error merging tag: %s", err)
return err
}
return nil
}); err != nil {
return nil, err
}
r.hookExecutor.ExecutePostHooks(ctx, t.ID, plugin.TagMergePost, input, nil)
return t, nil
}

View File

@@ -2,44 +2,71 @@ package api
import (
"context"
"fmt"
"path/filepath"
"strings"
"github.com/stashapp/stash/pkg/manager/config"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
"github.com/stashapp/stash/pkg/scraper/stashbox"
"golang.org/x/text/collate"
)
func (r *queryResolver) Configuration(ctx context.Context) (*models.ConfigResult, error) {
return makeConfigResult(), nil
}
func (r *queryResolver) Directory(ctx context.Context, path *string) (*models.Directory, error) {
func (r *queryResolver) Directory(ctx context.Context, path, locale *string) (*models.Directory, error) {
directory := &models.Directory{}
var err error
col := newCollator(locale, collate.IgnoreCase, collate.Numeric)
var dirPath = ""
if path != nil {
dirPath = *path
}
currentDir := utils.GetDir(dirPath)
directories, err := utils.ListDir(currentDir)
currentDir := getDir(dirPath)
directories, err := listDir(col, currentDir)
if err != nil {
return directory, err
}
directory.Path = currentDir
directory.Parent = utils.GetParent(currentDir)
directory.Parent = getParent(currentDir)
directory.Directories = directories
return directory, err
}
func getDir(path string) string {
if path == "" {
path = fsutil.GetHomeDirectory()
}
return path
}
func getParent(path string) *string {
isRoot := path[len(path)-1:] == "/"
if isRoot {
return nil
} else {
parentPath := filepath.Clean(path + "/..")
return &parentPath
}
}
func makeConfigResult() *models.ConfigResult {
return &models.ConfigResult{
General: makeConfigGeneralResult(),
Interface: makeConfigInterfaceResult(),
Dlna: makeConfigDLNAResult(),
Scraping: makeConfigScrapingResult(),
Defaults: makeConfigDefaultsResult(),
UI: makeConfigUIResult(),
}
}
@@ -60,7 +87,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
DatabasePath: config.GetDatabasePath(),
GeneratedPath: config.GetGeneratedPath(),
MetadataPath: config.GetMetadataPath(),
ConfigFilePath: config.GetConfigFilePath(),
ConfigFilePath: config.GetConfigFile(),
ScrapersPath: config.GetScrapersPath(),
CachePath: config.GetCachePath(),
CalculateMd5: config.IsCalculateMD5(),
@@ -79,7 +106,6 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
Username: config.GetUsername(),
Password: config.GetPasswordHash(),
MaxSessionAge: config.GetMaxSessionAge(),
TrustedProxies: config.GetTrustedProxies(),
LogFile: &logFile,
LogOut: config.GetLogOut(),
LogLevel: config.GetLogLevel(),
@@ -95,6 +121,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
ScraperCertCheck: config.GetScraperCertCheck(),
ScraperCDPPath: &scraperCDPPath,
StashBoxes: config.GetStashBoxes(),
PythonPath: config.GetPythonPath(),
}
}
@@ -103,31 +130,50 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
menuItems := config.GetMenuItems()
soundOnPreview := config.GetSoundOnPreview()
wallShowTitle := config.GetWallShowTitle()
showScrubber := config.GetShowScrubber()
wallPlayback := config.GetWallPlayback()
noBrowser := config.GetNoBrowser()
notificationsEnabled := config.GetNotificationsEnabled()
maximumLoopDuration := config.GetMaximumLoopDuration()
autostartVideo := config.GetAutostartVideo()
autostartVideoOnPlaySelected := config.GetAutostartVideoOnPlaySelected()
continuePlaylistDefault := config.GetContinuePlaylistDefault()
showStudioAsText := config.GetShowStudioAsText()
css := config.GetCSS()
cssEnabled := config.GetCSSEnabled()
language := config.GetLanguage()
slideshowDelay := config.GetSlideshowDelay()
handyKey := config.GetHandyKey()
scriptOffset := config.GetFunscriptOffset()
imageLightboxOptions := config.GetImageLightboxOptions()
// FIXME - misnamed output field means we have redundant fields
disableDropdownCreate := config.GetDisableDropdownCreate()
return &models.ConfigInterfaceResult{
MenuItems: menuItems,
SoundOnPreview: &soundOnPreview,
WallShowTitle: &wallShowTitle,
WallPlayback: &wallPlayback,
MaximumLoopDuration: &maximumLoopDuration,
AutostartVideo: &autostartVideo,
ShowStudioAsText: &showStudioAsText,
CSS: &css,
CSSEnabled: &cssEnabled,
Language: &language,
SlideshowDelay: &slideshowDelay,
HandyKey: &handyKey,
FunscriptOffset: &scriptOffset,
MenuItems: menuItems,
SoundOnPreview: &soundOnPreview,
WallShowTitle: &wallShowTitle,
WallPlayback: &wallPlayback,
ShowScrubber: &showScrubber,
MaximumLoopDuration: &maximumLoopDuration,
NoBrowser: &noBrowser,
NotificationsEnabled: &notificationsEnabled,
AutostartVideo: &autostartVideo,
ShowStudioAsText: &showStudioAsText,
AutostartVideoOnPlaySelected: &autostartVideoOnPlaySelected,
ContinuePlaylistDefault: &continuePlaylistDefault,
CSS: &css,
CSSEnabled: &cssEnabled,
Language: &language,
ImageLightbox: &imageLightboxOptions,
// FIXME - see above
DisabledDropdownCreate: disableDropdownCreate,
DisableDropdownCreate: disableDropdownCreate,
HandyKey: &handyKey,
FunscriptOffset: &scriptOffset,
}
}
@@ -155,3 +201,57 @@ func makeConfigScrapingResult() *models.ConfigScrapingResult {
ExcludeTagPatterns: config.GetScraperExcludeTagPatterns(),
}
}
func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult {
config := config.GetInstance()
deleteFileDefault := config.GetDeleteFileDefault()
deleteGeneratedDefault := config.GetDeleteGeneratedDefault()
return &models.ConfigDefaultSettingsResult{
Identify: config.GetDefaultIdentifySettings(),
Scan: config.GetDefaultScanSettings(),
AutoTag: config.GetDefaultAutoTagSettings(),
Generate: config.GetDefaultGenerateSettings(),
DeleteFile: &deleteFileDefault,
DeleteGenerated: &deleteGeneratedDefault,
}
}
func makeConfigUIResult() map[string]interface{} {
return config.GetInstance().GetUIConfiguration()
}
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input models.StashBoxInput) (*models.StashBoxValidationResult, error) {
client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager)
user, err := client.GetUser(ctx)
valid := user != nil && user.Me != nil
var status string
if valid {
status = fmt.Sprintf("Successfully authenticated as %s", user.Me.Name)
} else {
switch {
case strings.Contains(strings.ToLower(err.Error()), "doctype"):
// Index file returned rather than graphql
status = "Invalid endpoint"
case strings.Contains(err.Error(), "request failed"):
status = "No response from server"
case strings.HasPrefix(err.Error(), "invalid character") ||
strings.HasPrefix(err.Error(), "illegal base64 data") ||
err.Error() == "unexpected end of JSON input" ||
err.Error() == "token contains an invalid number of segments":
status = "Malformed API key."
case err.Error() == "" || err.Error() == "signature is invalid":
status = "Invalid or expired API key."
default:
status = fmt.Sprintf("Unknown error: %s", err)
}
}
result := models.StashBoxValidationResult{
Valid: valid,
Status: status,
}
return &result, nil
}

View File

@@ -3,7 +3,7 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/manager"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
)

Some files were not shown because too many files have changed in this diff Show More