Compare commits

...

606 Commits

Author SHA1 Message Date
WithoutPants
8a649f0268 Rearrange changelog 2022-11-30 10:31:17 +11:00
WithoutPants
0b19a00ba8 Fix incorrect key being checked for override (#3212) 2022-11-30 09:34:05 +11:00
WithoutPants
5e332514fa Fix concurrent test timeout 2022-11-30 09:25:12 +11:00
stash-translation-bot
646f8bc02e Translations update from Stash (#3178)
* Translated using Weblate (Italian)

Currently translated at 100.0% (884 of 884 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 97.8% (865 of 884 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.5% (871 of 884 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% (885 of 885 strings)

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

* Translated using Weblate (Finnish)

Currently translated at 91.2% (808 of 885 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 100.0% (885 of 885 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (885 of 885 strings)

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

* Translated using Weblate (Russian)

Currently translated at 89.0% (788 of 885 strings)

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

* Translated using Weblate (Korean)

Currently translated at 97.5% (863 of 885 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (885 of 885 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (885 of 885 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 95.4% (845 of 885 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 97.1% (860 of 885 strings)

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

* Translated using Weblate (Polish)

Currently translated at 98.9% (876 of 885 strings)

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

* Translated using Weblate (Korean)

Currently translated at 97.7% (865 of 885 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (885 of 885 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (885 of 885 strings)

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

* Translated using Weblate (Czech)

Currently translated at 59.0% (523 of 885 strings)

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

Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: kkk <850345193@qq.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: MrOV3RDOSE <mrov3rdose@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: ApxuBbI <nick1232@inbox.lv>
Co-authored-by: alan <wnd1@daum.net>
Co-authored-by: Lauri <stashapp.pot3l@8shield.net>
Co-authored-by: Ikko Ashimine <eltociear@gmail.com>
Co-authored-by: suzuhiroruri <suzuhiroruri@gmail.com>
Co-authored-by: Jin YunHo <justin4758@naver.com>
Co-authored-by: Foucoubou <foucoubou26@email.cz>
2022-11-29 02:14:58 -05:00
WithoutPants
9350be12d9 Fix sortdir not being set in saved filter (#3206) 2022-11-29 12:56:03 +11:00
CJ
02ec98b302 fix card clipping on recomendation row (#3205) 2022-11-29 12:02:52 +11:00
CJ
a8f4c2c29c Reorder new scene fields (#3191) 2022-11-27 07:45:26 +11:00
WithoutPants
57ad12e43b Improve gallery performance (#3183)
* Improve cover resolver performance
* Deprecate and remove usage of slow gallery images
2022-11-25 11:14:50 +11:00
WithoutPants
f0a3a3dd44 Handle bad funscript at values (#3182) 2022-11-25 08:20:23 +11:00
WithoutPants
27998c35a1 Fix buttons not working correct in number filter (#3177) 2022-11-24 08:11:40 +11:00
WithoutPants
03cd9529bd Fix NaN weight in performer scrape dialog 2022-11-23 17:35:44 +11:00
puc9
3072333118 Adding info about duplicate files in various places in the UI (#3054)
* Add counter to File Info where file count > 1
* Add file modification time to File Info panel
* Remove duplicate intl keys
* Add file count to duplicate checker
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-23 17:30:51 +11:00
DingDongSoLong4
821587b166 Player mobile improvements (#3120)
* Add videojs-mobile-ui
* Prevent marker wrapping and fix alignment
* Fix marker update on delete
* Change hotkey modifier behaviour
* Update KeyboardShortcuts.md
2022-11-23 15:55:24 +11:00
WithoutPants
b175f1865f Remove python3/python link
Looks like this is already present in the latest alpine image
2022-11-23 14:52:23 +11:00
kermieisinthehouse
5a2242e78d I18N: Add Estonian, Russian first-level langs, add many preview langs (#3176)
* Add Estonian, Russian first-level langs, add many preview langs

* Changelog
2022-11-22 18:27:47 -05:00
WithoutPants
54c495d867 Remove netgo build tag from Windows builds (#3170) 2022-11-23 09:10:39 +11:00
stash-translation-bot
e84221ccbe Translations update from Stash (#3024)
* Translated using Weblate (Japanese)

Currently translated at 100.0% (845 of 845 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 97.7% (826 of 845 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% (845 of 845 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 97.7% (826 of 845 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 97.7% (826 of 845 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 97.7% (826 of 845 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 97.7% (826 of 845 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 97.7% (826 of 845 strings)

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

* Translated using Weblate (Czech)

Currently translated at 58.4% (494 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 97.7% (826 of 845 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 99.8% (844 of 845 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 99.8% (844 of 845 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 97.7% (826 of 845 strings)

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

* Added translation using Weblate (Estonian)

* Translated using Weblate (Estonian)

Currently translated at 0.5% (5 of 845 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 37.2% (315 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 97.8% (827 of 845 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 97.8% (827 of 845 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 97.8% (827 of 845 strings)

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

* Translated using Weblate (Danish)

Currently translated at 97.5% (824 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 99.2% (839 of 845 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 99.2% (839 of 845 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 99.2% (839 of 845 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Estonian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.8% (844 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 75.9% (642 of 845 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 85.6% (724 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 85.6% (724 of 845 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.8% (844 of 845 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 19.5% (165 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 strings)

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

* Added translation using Weblate (Bengali)

* Translated using Weblate (Russian)

Currently translated at 91.0% (769 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 91.0% (769 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (845 of 845 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 99.7% (843 of 845 strings)

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

* Translated using Weblate (Russian)

Currently translated at 90.8% (768 of 845 strings)

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

* Translated using Weblate (Bengali (Bangladesh))

Currently translated at 17.1% (145 of 845 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (845 of 845 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% (846 of 846 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% (846 of 846 strings)

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

* Translated using Weblate (Russian)

Currently translated at 90.7% (768 of 846 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (848 of 848 strings)

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

* Translated using Weblate (Russian)

Currently translated at 90.5% (768 of 848 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (848 of 848 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (848 of 848 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (847 of 848 strings)

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

* Added translation using Weblate (Persian)

* Translated using Weblate (Persian)

Currently translated at 0.7% (6 of 848 strings)

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

* Translated using Weblate (Russian)

Currently translated at 90.5% (768 of 848 strings)

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

* Translated using Weblate (Bengali (Bangladesh))

Currently translated at 26.6% (226 of 848 strings)

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

* Translated using Weblate (Swedish)

Currently translated at 100.0% (848 of 848 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 99.6% (845 of 848 strings)

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

* Translated using Weblate (Russian)

Currently translated at 91.1% (773 of 848 strings)

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

Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: MrOV3RDOSE <mrov3rdose@gmail.com>
Co-authored-by: yc <yechan24680@gmail.com>
Co-authored-by: Foucoubou <foucoubou26@email.cz>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: HappyAxolotl <qfozc3kmkzc@temp.mailbox.org>
Co-authored-by: Lauri Lepik <stashapp.pot3l@8shield.net>
Co-authored-by: An der <oebro@duck.com>
Co-authored-by: jimao <zhuzibintc@163.com>
Co-authored-by: brestu <brestu@protonmail.com>
Co-authored-by: ApxuBbI <nick1232@inbox.lv>
Co-authored-by: vohibi harcity <vohibi9657@harcity.com>
Co-authored-by: floordiv <real.floordiv@gmail.com>
Co-authored-by: Nayan Hossen <yarbutt2005@gmail.com>
Co-authored-by: Klim Waeknowing <wae@waeknowing.ml>
Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: lhDream <1107053351@qq.com>
Co-authored-by: guoard <afsharzadeh8@gmail.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: kermieisinthehouse <kermie@isinthe.house>
2022-11-22 16:31:46 -05:00
WithoutPants
8b59a3b014 Fix hang in concurrency tests 2022-11-22 12:24:18 +11:00
WithoutPants
f76a440e54 Update changelog 2022-11-22 10:23:32 +11:00
WithoutPants
aafbba7d77 Prevent hang when deleting while streaming (#3169) 2022-11-22 10:21:27 +11:00
WithoutPants
7bb35b2b09 Handle index.html correctly in custom served folders (#3168)
* getStringMapString return nil if key not found
* Refactor custom routes. Handle /index.html
2022-11-22 10:21:15 +11:00
WithoutPants
af28fd0f3b Fix duplicate downloaded from field 2022-11-21 17:28:49 +11:00
alexandra-3
abc9ec648a Fix a few cases where ffmpeg produces no output (#3161)
* Treat no output from ffmpeg as an error condition
* Distinguish file vs. video duration, and use later where appropriate
* Check for empty file in generateFile

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-21 17:21:27 +11:00
WithoutPants
045ba55def Fix startPosition not honoured 2022-11-21 14:57:57 +11:00
CJ
0664c5b974 Track watch activity for scenes. (#3055)
* track watchtime and view time
* add view count sorting, added continue position filter
* display metrics in file info
* add toggle for tracking activity
* save activity every 10 seconds
* reset resume when video is nearly complete
* start from beginning when playing scene in queue

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-21 12:55:15 +11:00
WithoutPants
f39fa416a9 Fix database locked errors (#3153)
* Make read-only operations use WithReadTxn
* Allow one database write thread
* Add unit test for concurrent transactions
* Perform some actions after commit to release txn
* Suppress some errors from cancelled context
2022-11-21 06:49:10 +11:00
DingDongSoLong4
420c6fa9d7 Fix duplicate log messages (#3116) 2022-11-18 14:39:30 +11:00
WithoutPants
b1c00a64fc Remove performer age image filter criterion (#3143) 2022-11-17 14:56:17 +11:00
WithoutPants
4ff163d375 Add ResizeObserver polyfill for safari (#3142) 2022-11-17 13:50:41 +11:00
WithoutPants
c1f271fc52 Trim database path in migrate page (#3140) 2022-11-17 12:26:20 +11:00
WithoutPants
2cce547986 Fix regression in marker title generation (#3141) 2022-11-17 12:11:10 +11:00
WithoutPants
f0bf780c2e Use RatingSystem control in RatingFilter (#3133)
* Use RatingSystem control in RatingFilter
* Improve styling for rating on performer page
2022-11-17 10:10:40 +11:00
stg-annon
3660bf2d1a Support filtering by StashID endpoint (#3005)
* Add endpoint to stash_id filter in UI

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-17 10:08:15 +11:00
CJ
ca9c8e0a34 Support Javascript injection (#3132) 2022-11-17 09:37:06 +11:00
WithoutPants
3a63f1f9b7 Fire hook when gallery created from image (#3134) 2022-11-17 08:23:08 +11:00
bnkai
ffca8f0c0f Fix rating in SceneCreateInput graphql schema (#3137)
* Fix rating in SceneCreateInput graphql schema
* Fix rating not set at creation

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-17 08:22:11 +11:00
WithoutPants
0443439fae Show error if savedFilterId not present (#3129) 2022-11-16 10:09:50 +11:00
WithoutPants
dc820e29af Revert removal of chapters_vtt from ScenePathsType (#3128) 2022-11-16 09:45:45 +11:00
skier233
7eae751d1c Stash rating system (#2830)
* add rating100 fields to represent rating range 1-100
* deprecate existing (1-5) rating fields
* add half- and quarter-star options for rating system
* add decimal rating system option

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-11-16 09:31:44 +11:00
gitgiggety
f66333bac9 Add date filters (#2834)
* graphql: support date and timestamp filter types
* sql: add support for date & timestamp criterions
* ui: add support for date and timestamp criterions
* scenes: add support for filtering by date, created at and updated at
* image: support filtering by created at and updated at
* gallery: support filtering by date, created at and updated at
* movie: support filtering by date, created at and updated at
* studio: support filtering by date, created at and updated at
* tag: support filtering by date, created at and updated at
* performer: support filtering by bitrh & death date and created & updated at
* marker: support filtering by created & updated at and scene date, created & updated at
2022-11-15 11:52:05 +11:00
WithoutPants
ce17230c13 Refactor autotag to use individual transactions (#3106)
* Add id filtering to scenes, images, and galleries
* Perform tagging in batches
* One transaction per object tagged
2022-11-14 17:07:24 +11:00
WithoutPants
4a054ab081 Support file-less scenes. Add scene split, merge and reassign file (#3006)
* Reassign scene file functionality
* Implement scene create
* Add scene create UI
* Add sceneMerge backend support
* Add merge scene to UI
* Populate split create with scene details
* Add merge button to duplicate checker
* Handle file-less scenes in marker preview generate
* Make unique file name for file-less scene exports
* Add o-counter to scene update input
* Hide rescan for file-less scenes
* Generate heatmap if no speed set on file
* Fix count in scene/image queries
2022-11-14 16:35:09 +11:00
WithoutPants
d0b0be4dd4 Fix test database generator (#3112) [skip ci] 2022-11-10 15:01:47 +11:00
WithoutPants
9df66024d1 Make title optional for non-user created galleries (#3110) 2022-11-10 14:19:13 +11:00
WithoutPants
c83ebf7c1c Order performers by name in All (#3111) 2022-11-10 14:18:46 +11:00
WithoutPants
eb795ff9ab Add new fields to scene tagger (#3094)
* Add new fields to scene tagger
* Update scraper docs with new fields
* Set code and director in identify
* Add new fields to identify dialog
2022-11-10 12:51:49 +11:00
WithoutPants
a2ca266cb3 Upgrade to go 1.19 only (#3087)
* Update to go 1.19
* Update cross-compile script
* Add missing targets to cross-compile-all
* Update cache action to remove warning
2022-11-09 14:41:23 +11:00
WithoutPants
3bc0de3f3a Show imperial units for height and weight (#3097)
* Show imperial units for height and weight
* Fix migration note index
2022-11-09 11:10:57 +11:00
WithoutPants
30a7482ddf Order saved filters by name (#3101) 2022-11-09 11:06:11 +11:00
WithoutPants
04514fbc88 Fix changelog 2022-11-08 14:43:31 +11:00
WithoutPants
f1d57c3d62 Update changelog 2022-11-08 14:42:41 +11:00
wildsolutionbroadcast
404a68c994 Limiting how many options are shown in select dropdowns (#3062)
Introducing a limit to how many options are shown in select dropdowns. Fixes an issue I was experiencing where large numbers of options (5000 tags) was causing dropdown to be unresponsive. Does not effect filtering, always shows 'Create "..."' option if it exists, and shows a notice at the bottom of the dropdown of how many options were hidden from the list if any were.
2022-11-08 14:41:24 +11:00
WithoutPants
d2743cf5fb Change performer height to be numeric (#3060)
* Make height an int. Add height_cm field
* Change UI to use height_cm
* Use number fields for height/weight
* Add migration note
2022-11-08 14:09:03 +11:00
WithoutPants
b9e07ade92 Fix seeking (#3096)
* Update apikey when generating/clearing
* Fix seeking on systems with api key
2022-11-08 13:45:54 +11:00
WithoutPants
962bc7df4e Fix panic when fileSize is negative (#3089) 2022-11-08 10:01:32 +11:00
DogmaDragon
f1c454eb09 Typo (#3090) 2022-11-07 18:41:47 +11:00
HappyAxolotl
eff86bf2f8 [Feature] Add fields director and (studio) code to scenes (#3051)
* added schema migration and updated data models
* added code and director to UI
* new fields are exported and imported
* added filters
* Add changelog entry
2022-11-07 18:16:52 +11:00
DingDongSoLong4
7540d3b477 Fix react hook order errors (#3081)
* Fix react hook order error on tag page
* Make all filter hooks actual hooks
2022-11-07 15:49:37 +11:00
DingDongSoLong4
653db3cc1d Scene player improvements (#3020)
* Add types to player plugins
* Use videojs-vtt.js to parse sprite VTT files
* Overhaul scene player
* Replace vtt-thumbnails-freetube
* Remove chapters_vtt
* Force remove shadow from player progress bar
* Cleanup player css
* Rewrite live.ts as middleware
* Don't force play when changing source
2022-11-07 14:53:12 +11:00
WithoutPants
2609095c7a Revert "Upgrade to go 1.19 and update dependencies (#3069)" (#3085)
This reverts commit bba7c23957.
2022-11-07 12:33:15 +11:00
WithoutPants
bba7c23957 Upgrade to go 1.19 and update dependencies (#3069)
* Update to go 1.19
* Update dependencies
* Update cross-compile script
* Add missing targets to cross-compile-all
* Update cache action to remove warning
2022-11-04 13:41:26 +11:00
WithoutPants
f25881a3bf Use alpha2 for country label (#3067) 2022-11-03 09:04:48 +11:00
WithoutPants
270bc317cb Performer refactor (#3057)
* Separate performer model from sqlite model
* Use GenderEnum for gender
2022-10-31 14:58:01 +11:00
DingDongSoLong4
b1fa933868 Fix URL encoding again (#3044)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-10-28 17:46:39 +11:00
DingDongSoLong4
2cd9ef6302 Fix vite circular dependency warning 2022-10-28 06:16:13 +00:00
DingDongSoLong4
db29246883 Fix subtitle error typo (#3056) 2022-10-28 16:38:50 +11:00
InfiniteTF
7b7d6758ef Change performer country value to be ISO code (#1922)
* Change performer country value to be ISO code
* Localize country names
* Use country select for filter

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-10-28 16:37:57 +11:00
DingDongSoLong4
1c0042c4c2 Fix path filters (#3041)
* Fix path filters
* Replace getPathSearchClause
* Remove incorrect tests
2022-10-26 18:48:13 +11:00
stg-annon
a60afc162f Add Tag description filter (#3011)
* init tag description filter

issue with rendering UI filter dialog

* move description to string block
* Add 0.18.0 changelog page
2022-10-26 18:06:52 +11:00
puc9
2b80b6d8d5 Minor UI fix for StashId display in FileInfoPanel (#3050) 2022-10-26 13:57:29 +11:00
A Ghoul Coder
3ac3fe09b8 add pre-ui to default make target and update documentation (#3030)
Without `pre-ui`, `make` fails. `pre-ui` is already used in the GitHub
workflows but omitted from the Makefile and docs.
2022-10-26 09:18:02 +11:00
WithoutPants
02c2ad3f58 Update changelog 2022-10-25 11:42:27 +11:00
WithoutPants
479ebfc88d Reimplement case-insensitivity move bug fix (#3047)
* Use eq for FindByPath for case sensitivity
* Handle case sensitive moves
2022-10-25 11:37:54 +11:00
WithoutPants
1c92336798 Fix symlink size calculation (#3046) 2022-10-25 10:57:37 +11:00
WithoutPants
5fae3cf127 Update changelog 2022-10-24 14:40:51 +11:00
WithoutPants
47395ce13f Use basename as title if empty when scraping by fragment (#3040)
* Fallback to file basename if title empty in scrape
* Populate dialog from basename if title empty
2022-10-24 14:36:22 +11:00
WithoutPants
091950615e Ignore non-existing scenes in fingerprint submits (#3039) 2022-10-24 10:26:21 +11:00
DingDongSoLong4
4db0e48f73 Fix zip gallery renaming (#3036) 2022-10-24 09:38:02 +11:00
WithoutPants
33de28ce5d Warn when failed to migrate from placeholder 2022-10-20 01:18:32 +00:00
WithoutPants
b8a8909a8e Add hotfix changelogs 2022-10-20 01:02:13 +00:00
WithoutPants
0cf06728d4 Ignore NULL values during migration 2022-10-20 01:01:18 +00:00
WithoutPants
3acece2438 Fix export zip paths when exporting from Windows (#3022)
* Use correct zip path for export in windows
* Fix recursive loop when importing tag hierarchy
2022-10-20 11:41:46 +11:00
WithoutPants
7104bb67ca Handle null video durations in migration (#3021) 2022-10-20 10:58:42 +11:00
WithoutPants
c4c6a3f9c0 Add Ukrainian language option 2022-10-19 22:40:55 +00:00
stash-translation-bot
86b52fe938 Translations update from Stash (#2931)
* Translated using Weblate (Italian)

Currently translated at 100.0% (826 of 826 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% (826 of 826 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% (826 of 826 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (826 of 826 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (823 of 826 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (829 of 829 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% (829 of 829 strings)

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

* Translated using Weblate (Czech)

Currently translated at 59.5% (494 of 829 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (829 of 829 strings)

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

* Added translation using Weblate (Ukrainian)

* Translated using Weblate (Ukrainian)

Currently translated at 11.0% (92 of 829 strings)

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

* Translated using Weblate (Swedish)

Currently translated at 100.0% (829 of 829 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (837 of 837 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% (837 of 837 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% (837 of 837 strings)

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

* Translated using Weblate (French)

Currently translated at 98.5% (825 of 837 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 98.5% (825 of 837 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (845 of 845 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% (845 of 845 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (845 of 845 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 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 97.7% (826 of 845 strings)

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

* Translated using Weblate (Italian)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 97.6% (825 of 845 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (845 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 97.6% (825 of 845 strings)

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

* Translated using Weblate (Dutch)

Currently translated at 93.3% (789 of 845 strings)

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

* Translated using Weblate (French)

Currently translated at 98.1% (829 of 845 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 98.2% (830 of 845 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 97.7% (826 of 845 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 98.6% (834 of 845 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 98.3% (831 of 845 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 97.7% (826 of 845 strings)

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

Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: ponei <poneialt@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Foucoubou <foucoubou26@email.cz>
Co-authored-by: Still <dev@stillu.cc>
Co-authored-by: John <erse@tutanota.com>
Co-authored-by: MrOV3RDOSE <mrov3rdose@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Luc <luc@lucspoelder.nl>
Co-authored-by: bogay <pojay11523@gmail.com>
Co-authored-by: Phasetime <phasetime@protonmail.com>
Co-authored-by: brestu <brestu@protonmail.com>
2022-10-19 17:02:37 -04:00
WithoutPants
a64e0929d4 Update notes 2022-10-18 23:12:22 +00:00
WithoutPants
4c286d7ab5 Fix dataloaders not loading in js plugins (#3014)
* Include dataloaders in js plugin gql calls
* Handle gql errors correctly in js plugins
2022-10-18 11:09:54 +11:00
WithoutPants
bd44571a91 Fix video looping instead of continuing playlist (#3007)
* Fix loop overriding continue queue
* Add id to queue continue checkbox
2022-10-14 11:21:26 +11:00
WithoutPants
396c1ffc6d Format 2022-10-12 06:10:15 +00:00
WithoutPants
6dcb1279a7 Fix + character not handled correctly in query URL 2022-10-12 16:36:02 +11:00
WithoutPants
5e1948516d Fix phash match presentation (#2997) 2022-10-12 11:06:32 +11:00
WithoutPants
99bbd157d6 Clarify backup database description 2022-10-11 14:28:31 +11:00
WithoutPants
6488a4236e Update changelog 2022-10-11 14:27:26 +11:00
WithoutPants
a6fd577f03 Fix video playback hanging at end (#2996)
Co-authored-by: gerit1a <10052885+gerit1a@users.noreply.github.com>
2022-10-11 14:24:09 +11:00
WithoutPants
6b5d5cc628 Create missing covers during scan (#2995)
* Create missing covers during scan
* Update changelog and release notes
2022-10-11 14:22:23 +11:00
WithoutPants
e3cd36f25f Use primary tag name as marker title where title is empty (#2994)
* Fix display of marker popovers
* Use primary tag as title where marker title empty
2022-10-11 14:21:56 +11:00
WithoutPants
68a1547e8b Include primary tag in tag marker count sorting (#2993) 2022-10-11 14:21:28 +11:00
WithoutPants
9bff498c28 Fix tag/studio alias and caption null filtering (#2990)
* Fix null filter for alias/captions
* Fix error when selecting is null for captions
2022-10-11 14:21:07 +11:00
WithoutPants
6ce409cd56 Prevent errors from stopping scan 2022-10-11 14:20:24 +11:00
JackDawson94
b1193227d0 Fixes gender-mapping with StashBox (#2992) 2022-10-11 09:40:14 +11:00
WithoutPants
90fdc6b322 Fix direct streaming 2022-10-10 18:54:55 +11:00
WithoutPants
043b67e076 Fix setup/migrate redirects on subpath proxies (#2982) 2022-10-10 10:12:07 +11:00
WithoutPants
6c04f9199f Add apikey to streams (#2981) 2022-10-10 10:11:51 +11:00
CJ
351dcb708b Fix subtitles not loading (#2987) 2022-10-10 10:09:28 +11:00
7dJx1qP
bb250d1232 fix padding for non-checkbox cells (#2980) 2022-10-07 12:01:37 +11:00
WithoutPants
0e0d201ff3 Ignore other conflicts in 35 migration 2022-10-06 14:54:14 +11:00
WithoutPants
e96a09d9fd Update changelog 2022-10-06 14:51:32 +11:00
WithoutPants
ef9e138a2d [Files Refactor] Object file management (#2790)
* Add Make Primary file function
* Add delete file functionality
2022-10-06 14:50:06 +11:00
7dJx1qP
83359b00d5 Make scenes page list view checkbox fill entire cell (#2974) 2022-10-06 13:08:43 +11:00
Joe Scylla
9083796a42 #1810 Truncate large numbers on buttons (#2781)
* #1810 Truncate large numbers on buttons
* Apply to card popovers as well

Co-authored-by: Roland Karle <roland.karle@aufwind-group.de>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-10-06 12:43:17 +11:00
WithoutPants
b160c3bb97 Associate funscript files on scan (#2978) 2022-10-06 11:17:01 +11:00
WithoutPants
55001ddcf1 Ignore conflicts on performers_galleries 2022-10-06 10:05:43 +11:00
pickleahead
4c73f2f845 Add descriptions to tags and display tag cards on hover (#2708)
* add descriptions to tags
* display tag description and tag image on hover

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-10-06 10:01:06 +11:00
WithoutPants
480ae46dde Update changelog 2022-10-03 13:02:28 +11:00
WithoutPants
6ba9f55df0 Add default thumbnails for scenes and images (#2949)
* Use default thumbnail for scene covers
* Use defautl thumbnail for image thumbnails
2022-10-03 13:01:35 +11:00
WithoutPants
88bfda1980 Ignore duplicates for movies_scenes migration 2022-10-03 08:20:04 +11:00
WithoutPants
060ac00fc7 Take out _stash_ids from 35 migration 2022-10-01 08:45:16 +10:00
WithoutPants
7b83d81820 Fix backup issues (#2966)
Make backup directory setting optional. Use the basename of the database file for the naming.
2022-09-30 20:57:28 +10:00
WithoutPants
9e44e13f6d Fix update duplicate ids (#2965) 2022-09-30 18:44:37 +10:00
WithoutPants
51f4dd8a59 Delete identical studio stash id rows 2022-09-30 18:10:55 +10:00
WithoutPants
30f7a05ebf Delete identical rows from performer_stash_ids 2022-09-30 17:25:40 +10:00
WithoutPants
8f594e7fed Fix migration 2022-09-30 16:23:50 +10:00
WithoutPants
c8cbb36fd5 Ignore multiple identical rows in performers_scenes 2022-09-30 15:59:29 +10:00
JackDawson94
554448594c Add unix timestamp parsing to scrapers parseDate (#2817)
* Add unix timestamp parsing to scrapers parseDate
* Add documentation
* Update ScraperDevelopment.md
* Add unit test

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-09-30 15:35:56 +10:00
WithoutPants
6c6e0b6236 Fix saved front page filters being corrupted 2022-09-30 14:47:35 +10:00
WithoutPants
b8b62a36c6 Update changelog 2022-09-30 11:30:23 +10:00
HijackHornet
b588597f3e [Feature] Config option for sub content display (#2832)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-09-30 11:28:47 +10:00
JackDawson94
c63c06de1c Stashbox tagger reorder (#2840)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-09-30 11:26:37 +10:00
DingDongSoLong4
25bc750295 Performance improvements (#2925)
* Add sqlite_stat4 build tag
* Simplify studio filter criterion queries
* Prevent useList loading data before filter initialized
2022-09-30 10:49:51 +10:00
7dJx1qP
d274f86390 Add backup directory path setting (#2953)
* add backup directory path setting
* Don't default backup path
* handle migration backup path input when given filename or path

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-09-30 10:00:50 +10:00
WithoutPants
ad7fbce5f7 Rebuild association tables, ensure file-system-based galleries cannot be changed (#2955)
* Re-create tables to include primary keys
* Filesystem-based galleries cannot change images
2022-09-30 09:18:58 +10:00
WithoutPants
dce90a3ed9 Handle file rescan (#2951)
* Fire handlers when file updated or moved
* Create galleries as needed
* Clean empty galleries
* Handle cleaning zip folders when path changed
* Fix gallery association on duplicate images
* Re-create missing folder-based galleries
2022-09-28 16:08:00 +10:00
DingDongSoLong4
00820a8789 Suppress new context closed errors (#2947) 2022-09-26 11:41:28 +10:00
WithoutPants
d4e706daef Only set video algorithm after migration 12 (#2946) 2022-09-26 11:27:53 +10:00
WithoutPants
0848b02e93 Various bug fixes (#2945)
* Only update fingerprints if changed
* Fix panic when loading primary file fails
* Fix gallery/scene association
* Fix display of scene gallery in card
* Use natural_cs collation with paths for title sorting
2022-09-25 12:07:55 +10:00
DingDongSoLong4
4089a5fccc Fix JSON.parse console error (#2943) 2022-09-25 10:06:32 +10:00
HijackHornet
74191c73ed Custom localization (#2837)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-09-22 19:49:35 +10:00
dependabot[bot]
c10d53ba8e Bump moment from 2.29.2 to 2.29.4 in /ui/v2.5 (#2739)
Bumps [moment](https://github.com/moment/moment) from 2.29.2 to 2.29.4.
- [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.2...2.29.4)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-22 16:33:29 +10:00
halorrr
10655586b0 add ruby and faraday gem to stash for use in scrapers (#2707) 2022-09-22 16:14:40 +10:00
WithoutPants
b74428cb42 Various bug fixes (#2938)
* Don't recalculate MD5 if not enabled

Remove MD5 if oshash has changed and MD5 was not calculated.

* Fix panic in paged DLNA
* Prevent identical hashes in stash-box drafts
2022-09-21 15:39:41 +10:00
WithoutPants
cffcd9f4b8 Various bug fixes (#2935)
* Sort scene/image/gallery tags by name
* Calculate md5 if missing
* Prevent multiple folder create logs
2022-09-20 17:02:14 +10:00
WithoutPants
3fa7b470e7 More timestamp corrections (#2933)
* Fix incorrect timestamp updates
* Correct folder time fields
* Add migration with new indexes
* Correct mod_time format
* Add mod_time to data massage
2022-09-20 13:52:37 +10:00
WithoutPants
98e3610ade Fix gallery checksum resolver error (#2929) 2022-09-19 16:50:04 +10:00
DingDongSoLong4
8efbcc1c4d Suppress benign broken pipe and context closed warnings (#2927) 2022-09-19 15:01:40 +10:00
DingDongSoLong4
5e97ecd260 Set explicit SameSite=Lax on session cookie (#2926) 2022-09-19 14:56:05 +10:00
WithoutPants
1207629a76 Fix incorrectly formatted timestamps (#2918)
* Update updated_at when adding file to object
* Use models.SQLTimestamp for timestamps
* Add data massage to fix incorrect timestamps
2022-09-19 14:53:46 +10:00
WithoutPants
2564351265 Use post commit hook for post-create plugin hooks (#2920) 2022-09-19 14:53:06 +10:00
WithoutPants
0359ce2ed8 Fix bulk add movie to scenes and autotag transaction error (#2928)
* Fix bulk add movie to scene
* Fix already in transaction error for autotag
2022-09-19 14:52:40 +10:00
WithoutPants
648247aa00 Split by whitespace for path includes/excludes (#2919) 2022-09-16 14:45:08 +10:00
WithoutPants
90726086e5 Fix relationship not loaded panic (#2915) 2022-09-16 11:27:38 +10:00
DingDongSoLong4
7a75313a1c Fix URL encoding (#2899)
* Fix URL encoding
* Optimize nullable criterion encoding
2022-09-16 11:17:19 +10:00
TgSeed
593477cbe1 Performer/Studio/Tag/Gallery Create compnent has default name as search query (#2701)
* feat: Closes #2618
fix: New button is now available even if pathname ends with '/'
2022-09-16 11:05:33 +10:00
WithoutPants
781a767fb6 Fix https detection with reverse proxy (#2910) 2022-09-15 09:54:36 +10:00
WithoutPants
d558902dfb Update changelog 2022-09-14 14:29:57 +10:00
WithoutPants
7f5f1c7e0d Fix continue queue checkbox not persisting (#2895) 2022-09-14 14:22:44 +10:00
WithoutPants
e6b7d40784 Apply autostartVideoOnPlaySelected to queue (#2896) 2022-09-14 14:22:11 +10:00
WithoutPants
5db42f4882 Fix incorrect gallery when clicking queue item (#2897) 2022-09-14 14:21:30 +10:00
WithoutPants
df9c7594c7 Disable http2 and fix https detection (#2900) 2022-09-14 14:20:53 +10:00
WithoutPants
2368269e63 Handle error in newImageBox WalkDir (#2894) 2022-09-14 14:20:09 +10:00
WithoutPants
32911367b0 Remove files-refactor build automation 2022-09-14 14:00:50 +10:00
WithoutPants
c43c695f5d Merge pull request #2907 from stashapp/files-refactor 2022-09-14 13:36:46 +10:00
WithoutPants
8b79eaca67 [Files Refactor] Use batching for pre/post-migration (#2906)
* Use batching for pre/post-migration
* Clarify release notes
2022-09-14 10:57:00 +10:00
DingDongSoLong4
5c383da5ec Optimize database after migration (#2904) 2022-09-14 09:15:36 +10:00
WithoutPants
cfc8222b9a [Files Refactor] Cleanup (#2893)
* Clean up notes for develop merge
* Remove commented code
* Lint
2022-09-07 16:50:15 +10:00
WithoutPants
9e08edc76f [Files Refactor] Don't require fingerprint calculation post-migrate (#2892) 2022-09-07 14:21:10 +10:00
WithoutPants
cc9fc2150e Fix scan settings not persisting (#2888) 2022-09-06 07:04:52 +00:00
WithoutPants
13bdba5b24 Fix json filename generation (#2887) 2022-09-06 07:04:52 +00:00
WithoutPants
0c513a604d Fix stack overflow creating windows network folders (#2886) 2022-09-06 07:04:52 +00:00
WithoutPants
276f14cdcb Fix filename generation in export (#2883) 2022-09-06 07:04:52 +00:00
WithoutPants
6b0bcdea88 [Files Refactor] Set primary flag when cleaning (#2880)
* Ensure single primary per object
* Set primary file during clean
* Only show migration notes for actual migrations
2022-09-06 07:04:52 +00:00
WithoutPants
7159ab69a3 Use DisplayName in autotag errors (#2873) 2022-09-06 07:04:52 +00:00
WithoutPants
94d39da706 [Files Refactor] Bug fixes (#2868)
* Return error if multiple rows returned for id
* Add missing LoadFiles calls
* Show id if path is empty
2022-09-06 07:04:52 +00:00
WithoutPants
273cf0383d [Files Refactor] Performance tuning (#2865)
* Don't load image files by default
* Don't load gallery files by default
* Don't load scene files by default
* Retry locked transactions forever
* Don't show release notes if config not loaded
* Don't translate path slashes in export
2022-09-06 07:04:52 +00:00
WithoutPants
0b534d89c6 [Files Refactor] Import export fixup (#2763)
* Adjust json schema
* Remove mappings file from export
* Import file/folder support
* Update documentation
* Make gallery filenames unique
2022-09-06 07:04:52 +00:00
WithoutPants
1222b7b87b Fix files not being cleaned correctly (#2862) 2022-09-06 07:04:52 +00:00
WithoutPants
7b439556c0 [Files Refactor] Rollback platform-agnostic paths (#2852)
* Rollback platform agnostic path storage
* Add release note for database change
2022-09-06 07:04:52 +00:00
WithoutPants
0c7b5cf6a1 [Files Refactor] Fix lint github action error (#2853)
* Use alternative config file to disable linters that cause out of mem error
2022-09-06 07:04:52 +00:00
WithoutPants
f4825fadf4 [Files refactor] Bug fixes (#2849)
* Fix scene sorting
* Fix folder-based gallery path sorting
* Fix gallery path filter
* Fix stash-box performer submission
* Fix identify logging
* Remove govet from linter
2022-09-06 07:03:43 +00:00
WithoutPants
00608c167a [Files Refactor] Performance tuning (#2819)
* Load scene relationships on demand
* Load image relationships on demand
* Load gallery relationships on demand
* Add dataloaden
* Use dataloaders
* Use where in for other find many functions
2022-09-06 07:03:42 +00:00
WithoutPants
9b31b20fed [Files Refactor] Performance tuning (#2813)
* Do database txn in same thread. Retry on locked db
* Remove captions from slimscenedata
* Fix tracing
* Use where in instead of individual selects
* Remove scenes_query view
* Remove image query view
* Remove gallery query view
* Use where in for FindMany
* Don't interrupt scanning zip files
* Fix image filesize sort
2022-09-06 07:03:42 +00:00
WithoutPants
87167736f6 [Files Refactor] bug fixes (#2811)
* Fix scan options not saving
* Fix duration stat calculation
2022-09-06 07:03:42 +00:00
WithoutPants
569c3a872a [Files Refactor] Performance tuning (#2809)
* Use cache during migration
* Avoid use of query views
* Use FindMany to find related objects
* Log slow queries
* Add folders to generated files
* Use SlimScene for scene queries
* Include filename in migration error message
2022-09-06 07:03:42 +00:00
WithoutPants
c825cf5d09 Correctly delete files when specified (#2804) 2022-09-06 07:03:42 +00:00
WithoutPants
5843fdcecc [Files Refactor] Migration fix (#2796)
* Fix large wal file during migration
* Fix migration dropping / from network share paths
2022-09-06 07:03:42 +00:00
WithoutPants
bc47932343 [Files Refactor] Performance tuning (#2784)
* Improve image query performance
* Tune queries
* Fix db generator
* Don't show release notes in setup
* Further tune indexes
* Log when creating screenshot
2022-09-06 07:03:42 +00:00
WithoutPants
abb574205a Files refactor fixes (#2743)
* Fix destroy gallery not destroying file
* Re-add minModTime functionality
* Deprecate useFileMetadata and stripFileExtension
* Optimise files post migration
* Decorate moved files. Use first missing file in move
* Include path in thumbnail generation error log
* Fix stash-box draft submission
* Don't destroy files unless deleting
* Call handler for files with no associated objects
* Fix moved zips causing error on scan
2022-09-06 07:03:42 +00:00
WithoutPants
461068462c [Files Refactor] Filter and sort by file count (#2744)
* Add filtering on file count
* Add sorting by file count
2022-09-06 07:03:42 +00:00
WithoutPants
5495d72849 File storage rewrite (#2676)
* Restructure data layer part 2 (#2599)
* Refactor and separate image model
* Refactor image query builder
* Handle relationships in image query builder
* Remove relationship management methods
* Refactor gallery model/query builder
* Add scenes to gallery model
* Convert scene model
* Refactor scene models
* Remove unused methods
* Add unit tests for gallery
* Add image tests
* Add scene tests
* Convert unnecessary scene value pointers to values
* Convert unnecessary pointer values to values
* Refactor scene partial
* Add scene partial tests
* Refactor ImagePartial
* Add image partial tests
* Refactor gallery partial update
* Add partial gallery update tests
* Use zero/null package for null values
* Add files and scan system
* Add sqlite implementation for files/folders
* Add unit tests for files/folders
* Image refactors
* Update image data layer
* Refactor gallery model and creation
* Refactor scene model
* Refactor scenes
* Don't set title from filename
* Allow galleries to freely add/remove images
* Add multiple scene file support to graphql and UI
* Add multiple file support for images in graphql/UI
* Add multiple file for galleries in graphql/UI
* Remove use of some deprecated fields
* Remove scene path usage
* Remove gallery path usage
* Remove path from image
* Move funscript to video file
* Refactor caption detection
* Migrate existing data
* Add post commit/rollback hook system
* Lint. Comment out import/export tests
* Add WithDatabase read only wrapper
* Prepend tasks to list
* Add 32 pre-migration
* Add warnings in release and migration notes
2022-09-06 07:03:42 +00:00
WithoutPants
30877c75fb Release notes dialog (#2726)
* Move manual docs
* Move changelog docs
* Add migration notes
* Move changelog to settings
* Add release notes dialog
* Add new changelog
2022-09-06 07:03:40 +00:00
WithoutPants
964b559309 Restructure data layer (#2532)
* Add new txn manager interface
* Add txn management to sqlite
* Rename get to getByID
* Add contexts to repository methods
* Update query builders
* Add context to reader writer interfaces
* Use repository in resolver
* Tighten interfaces
* Tighten interfaces in dlna
* Tighten interfaces in match package
* Tighten interfaces in scraper package
* Tighten interfaces in scan code
* Tighten interfaces on autotag package
* Remove ReaderWriter usage
* Merge database package into sqlite
2022-09-06 07:03:40 +00:00
WithoutPants
7b5bd80515 Separate graphql API from rest of the system (#2503)
* Move graphql generated files to api
* Refactor identify options
* Remove models.StashBoxes
* Move ScraperSource to scraper package
* Rename field strategy enums
* Rename identify.TaskOptions to Options
2022-09-06 07:03:40 +00:00
halorrr
9dcf03eb70 show stashids on studio details (#2810) 2022-09-05 22:41:01 -07:00
v-helmholtz
628afce516 Fix unclosed div tag in login.html (#2846) 2022-09-05 22:34:57 -07:00
dependabot[bot]
f5f4cbef1e Bump vite from 2.7.1 to 2.9.13 in /ui/v2.5 (#2863)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 2.7.1 to 2.9.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v2.9.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:production
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-05 22:33:45 -07:00
stash-translation-bot
c387550c63 Translations update from Stash (#2838)
* 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 (Thai)

Currently translated at 31.1% (255 of 819 strings)

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

* Translated using Weblate (Thai)

Currently translated at 32.6% (267 of 819 strings)

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

* Translated using Weblate (Thai)

Currently translated at 37.3% (306 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 55.0% (451 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 99.5% (815 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 99.5% (815 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 56.1% (460 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 99.5% (815 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 62.3% (511 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 99.5% (815 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 66.4% (544 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 77.7% (637 of 819 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 96.4% (790 of 819 strings)

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

* Translated using Weblate (Russian)

Currently translated at 78.1% (640 of 819 strings)

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

Co-authored-by: Alpaca Serious <srhsgsef@gmail.com>
Co-authored-by: Pakin Kornthamonphokin <themorajr@gmail.com>
Co-authored-by: ApxuBbI <nick1232@inbox.lv>
Co-authored-by: MrOV3RDOSE <mrov3rdose@gmail.com>
Co-authored-by: onlyxxxhentai <onlyxxxhentai@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
2022-09-05 22:32:04 -07:00
kermieisinthehouse
30879389ec Fix golangci OOM (#2889)
* Fix golangci OOM

* Fix all lints
2022-09-05 22:12:59 -07:00
HijackHornet
90baf7a469 Fixed cross-compile doc command (#2825)
Current doc pointed at the old image of the cross compiler docker image.
Thanks bnkai for telling me :)
2022-08-28 23:02:36 -07:00
stash-translation-bot
10bc2c6689 Translations update from Stash (#2725)
* Translated using Weblate (French)

Currently translated at 94.1% (771 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 94.1% (771 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 94.2% (772 of 819 strings)

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

* Translated using Weblate (Czech)

Currently translated at 15.0% (123 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 96.7% (792 of 819 strings)

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

* Translated using Weblate (Czech)

Currently translated at 23.9% (196 of 819 strings)

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

* Translated using Weblate (Czech)

Currently translated at 37.9% (311 of 819 strings)

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

* Translated using Weblate (Czech)

Currently translated at 51.1% (419 of 819 strings)

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

* Translated using Weblate (Czech)

Currently translated at 51.2% (420 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 96.7% (792 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 96.7% (792 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 97.5% (799 of 819 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.5% (766 of 819 strings)

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

* 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 (Czech)

Currently translated at 57.5% (471 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 97.4% (798 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 98.5% (807 of 819 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% (819 of 819 strings)

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

* 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 (Danish)

Currently translated at 98.9% (810 of 819 strings)

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

* Translated using Weblate (French)

Currently translated at 98.5% (807 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 99.2% (813 of 819 strings)

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

* 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 (Danish)

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/da/

* Translated using Weblate (Dutch)

Currently translated at 89.9% (737 of 819 strings)

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

Co-authored-by: - <adr.web@hotmail.fr>
Co-authored-by: Foucoubou <foucoubou26@email.cz>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Aa <jarruraita@outlook.com>
Co-authored-by: BViking78 <bviking78@gmail.com>
Co-authored-by: Coscosname <coscosname@gmail.com>
Co-authored-by: Steff <sbeuze@gmail.com>
Co-authored-by: Phasetime <phasetime@protonmail.com>
Co-authored-by: 風林火山 <nezoko@digdig.org>
Co-authored-by: Porn Mancer <pornmancer1337@gmail.com>
Co-authored-by: MrOV3RDOSE <mrov3rdose@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: Hidden Hiddenson <078emil@protonmail.com>
Co-authored-by: cumladhier <jasper.belle@outlook.com>
2022-08-17 09:15:02 -07:00
WithoutPants
cba0fddf61 Fix release publishing 2022-07-26 14:42:18 +10:00
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
2357 changed files with 340278 additions and 93715 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

@@ -4,7 +4,6 @@ on:
push:
branches: [ develop, master ]
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:7
jobs:
build:
@@ -28,7 +27,7 @@ jobs:
run: docker pull $COMPILER_IMAGE
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-node_modules
with:
@@ -36,7 +35,7 @@ jobs:
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
- name: Cache UI build
uses: actions/cache@v2
uses: actions/cache@v3
id: cache-ui
env:
cache-name: cache-ui
@@ -45,17 +44,20 @@ jobs:
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
- name: Cache go build
uses: actions/cache@v2
uses: actions/cache@v3
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
@@ -142,34 +139,36 @@ 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: 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 +182,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:7
jobs:
golangci:
@@ -36,13 +36,13 @@ 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: latest
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
args: --modules-download-mode=vendor --timeout=3m
args: --modules-download-mode=vendor --timeout=5m
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
@@ -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

13
.gitignore vendored
View File

@@ -16,13 +16,19 @@
*.out
# GraphQL generated output
pkg/models/generated_*.go
internal/api/generated_*.go
ui/v2.5/src/core/generated-*.tsx
####
# Jetbrains
####
####
# Visual Studio
####
/.vs
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
@@ -30,6 +36,7 @@ ui/v2.5/src/core/generated-*.tsx
.idea/**/dictionaries
.idea/**/shelf
.vscode
.devcontainer
# Generated files
.idea/**/contentModel.xml
@@ -54,6 +61,6 @@ node_modules
*.db
stash
/stash
dist
.DS_Store
.DS_Store

View File

@@ -1,46 +1,50 @@
# options for analysis running
run:
timeout: 3m
timeout: 5m
modules-download-mode: vendor
linters:
disable-all: true
enable:
# Default set of linters from golangci-lint
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- 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 +83,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

137
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
@@ -24,9 +24,14 @@ endif
export CGO_ENABLED = 1
# including netgo causes name resolution to go through the Go resolver
# and isn't necessary for static builds on Windows
GO_BUILD_TAGS_WINDOWS := sqlite_omit_load_extension sqlite_stat4 osusergo
GO_BUILD_TAGS_DEFAULT = $(GO_BUILD_TAGS_WINDOWS) netgo
.PHONY: release pre-build
release: generate ui build-release
release: pre-ui generate ui build-release
pre-build:
ifndef BUILD_DATE
@@ -41,9 +46,22 @@ ifndef STASH_VERSION
$(eval STASH_VERSION := $(shell git describe --tags --exclude latest_develop))
endif
ifndef OFFICIAL_BUILD
$(eval OFFICIAL_BUILD := false)
endif
ifndef GO_BUILD_TAGS
$(eval GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT))
endif
# NOTE: the build target still includes netgo because we cannot detect
# Windows easily from the Makefile.
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 "$(GO_BUILD_TAGS)" $(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
@@ -60,33 +78,58 @@ cross-compile-windows: export GOARCH := amd64
cross-compile-windows: export CC := x86_64-w64-mingw32-gcc
cross-compile-windows: export CXX := x86_64-w64-mingw32-g++
cross-compile-windows: OUTPUT := -o dist/stash-win.exe
cross-compile-windows: GO_BUILD_TAGS := $(GO_BUILD_TAGS_WINDOWS)
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
cross-compile-macos-intel: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
# 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
cross-compile-macos-applesilicon: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
# 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: GO_BUILD_TAGS += netgo
cross-compile-freebsd: build-release-static
cross-compile-linux: export GOOS := linux
cross-compile-linux: export GOARCH := amd64
cross-compile-linux: OUTPUT := -o dist/stash-linux
cross-compile-linux: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
cross-compile-linux: build-release-static
cross-compile-linux-arm64v8: export GOOS := linux
cross-compile-linux-arm64v8: export GOARCH := arm64
cross-compile-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
cross-compile-linux-arm64v8: OUTPUT := -o dist/stash-linux-arm64v8
cross-compile-linux-arm64v8: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
cross-compile-linux-arm64v8: build-release-static
cross-compile-linux-arm32v7: export GOOS := linux
@@ -94,23 +137,35 @@ cross-compile-linux-arm32v7: export GOARCH := arm
cross-compile-linux-arm32v7: export GOARM := 7
cross-compile-linux-arm32v7: export CC := arm-linux-gnueabihf-gcc
cross-compile-linux-arm32v7: OUTPUT := -o dist/stash-linux-arm32v7
cross-compile-linux-arm32v7: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
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: GO_BUILD_TAGS := $(GO_BUILD_TAGS_DEFAULT)
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-intel
make cross-compile-macos-applesilicon
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 +175,12 @@ 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
.PHONY: generate-dataloaders
generate-dataloaders:
go generate -mod=vendor ./internal/api/loaders
# Regenerates stash-box client files
.PHONY: generate-stash-box-client
@@ -160,17 +219,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 +254,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 18 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/
FROM golang:1.19-alpine as backend
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,18 @@
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 ruby && pip install --no-cache-dir mechanicalsoup cloudscraper && gem install faraday
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

@@ -1,4 +1,4 @@
FROM golang:1.17
FROM golang:1.19
LABEL maintainer="https://discord.gg/2TsNFKt"
@@ -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=7
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"]

69
docs/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,69 @@
# 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 pre-ui` to install UI dependencies
2. Run `make generate` to create generated files
3. Run `make ui` to compile the frontend
4. 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 stashapp/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

104
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,82 +16,96 @@ 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/mattn/go-sqlite3 v1.14.6
github.com/json-iterator/go v1.1.12
github.com/mattn/go-sqlite3 v1.14.7
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-20220722155237-a158d28d115b
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
golang.org/x/text v0.3.7
golang.org/x/tools v0.1.12 // 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/doug-martin/goqu/v9 v9.18.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/dataloaden v0.3.0
github.com/vektah/gqlparser/v2 v2.4.1
gopkg.in/guregu/null.v4 v4.0.0
)
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
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
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.20220419223038-86c51ed26bb4 // 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
go 1.17
go 1.19

517
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -4,39 +4,122 @@ schema:
- "graphql/schema/types/*.graphql"
- "graphql/schema/*.graphql"
exec:
filename: pkg/models/generated_exec.go
filename: internal/api/generated_exec.go
model:
filename: pkg/models/generated_models.go
filename: internal/api/generated_models.go
resolver:
filename: pkg/api/resolver.go
filename: internal/api/resolver.go
type: Resolver
struct_tag: gqlgen
autobind:
- github.com/stashapp/stash/pkg/models
- github.com/stashapp/stash/pkg/plugin
- github.com/stashapp/stash/pkg/scraper
- github.com/stashapp/stash/internal/identify
- github.com/stashapp/stash/internal/dlna
- github.com/stashapp/stash/pkg/scraper/stashbox
models:
Gallery:
model: github.com/stashapp/stash/pkg/models.Gallery
# Scalars
Timestamp:
model: github.com/stashapp/stash/pkg/models.Timestamp
Int64:
model: github.com/stashapp/stash/pkg/models.Int64
# define to force resolvers
Image:
model: github.com/stashapp/stash/pkg/models.Image
ImageFileType:
model: github.com/stashapp/stash/pkg/models.ImageFileType
Performer:
model: github.com/stashapp/stash/pkg/models.Performer
Scene:
model: github.com/stashapp/stash/pkg/models.Scene
SceneMarker:
model: github.com/stashapp/stash/pkg/models.SceneMarker
ScrapedItem:
model: github.com/stashapp/stash/pkg/models.ScrapedItem
Studio:
model: github.com/stashapp/stash/pkg/models.Studio
Movie:
model: github.com/stashapp/stash/pkg/models.Movie
Tag:
model: github.com/stashapp/stash/pkg/models.Tag
SceneFileType:
model: github.com/stashapp/stash/pkg/models.SceneFileType
SavedFilter:
model: github.com/stashapp/stash/pkg/models.SavedFilter
StashID:
fields:
title:
resolver: true
# autobind on config causes generation issues
StashConfig:
model: github.com/stashapp/stash/internal/manager/config.StashConfig
StashConfigInput:
model: github.com/stashapp/stash/internal/manager/config.StashConfigInput
StashBoxInput:
model: github.com/stashapp/stash/internal/manager/config.StashBoxInput
ConfigImageLightboxResult:
model: github.com/stashapp/stash/internal/manager/config.ConfigImageLightboxResult
ImageLightboxDisplayMode:
model: github.com/stashapp/stash/internal/manager/config.ImageLightboxDisplayMode
ImageLightboxScrollMode:
model: github.com/stashapp/stash/internal/manager/config.ImageLightboxScrollMode
ConfigDisableDropdownCreate:
model: github.com/stashapp/stash/internal/manager/config.ConfigDisableDropdownCreate
ScanMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.ScanMetadataOptions
AutoTagMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.AutoTagMetadataOptions
SceneParserInput:
model: github.com/stashapp/stash/internal/manager.SceneParserInput
SceneParserResult:
model: github.com/stashapp/stash/internal/manager.SceneParserResult
SceneMovieID:
model: github.com/stashapp/stash/internal/manager.SceneMovieID
SystemStatus:
model: github.com/stashapp/stash/internal/manager.SystemStatus
SystemStatusEnum:
model: github.com/stashapp/stash/internal/manager.SystemStatusEnum
ImportDuplicateEnum:
model: github.com/stashapp/stash/internal/manager.ImportDuplicateEnum
SetupInput:
model: github.com/stashapp/stash/internal/manager.SetupInput
MigrateInput:
model: github.com/stashapp/stash/internal/manager.MigrateInput
ScanMetadataInput:
model: github.com/stashapp/stash/internal/manager.ScanMetadataInput
GenerateMetadataInput:
model: github.com/stashapp/stash/internal/manager.GenerateMetadataInput
GeneratePreviewOptionsInput:
model: github.com/stashapp/stash/internal/manager.GeneratePreviewOptionsInput
AutoTagMetadataInput:
model: github.com/stashapp/stash/internal/manager.AutoTagMetadataInput
CleanMetadataInput:
model: github.com/stashapp/stash/internal/manager.CleanMetadataInput
StashBoxBatchPerformerTagInput:
model: github.com/stashapp/stash/internal/manager.StashBoxBatchPerformerTagInput
SceneStreamEndpoint:
model: github.com/stashapp/stash/internal/manager.SceneStreamEndpoint
ExportObjectTypeInput:
model: github.com/stashapp/stash/internal/manager.ExportObjectTypeInput
ExportObjectsInput:
model: github.com/stashapp/stash/internal/manager.ExportObjectsInput
ImportObjectsInput:
model: github.com/stashapp/stash/internal/manager.ImportObjectsInput
ScanMetaDataFilterInput:
model: github.com/stashapp/stash/internal/manager.ScanMetaDataFilterInput
# renamed types
BulkUpdateIdMode:
model: github.com/stashapp/stash/pkg/models.RelationshipUpdateMode
DLNAStatus:
model: github.com/stashapp/stash/internal/dlna.Status
DLNAIP:
model: github.com/stashapp/stash/internal/dlna.Dlnaip
IdentifySource:
model: github.com/stashapp/stash/internal/identify.Source
IdentifyMetadataTaskOptions:
model: github.com/stashapp/stash/internal/identify.Options
IdentifyMetadataInput:
model: github.com/stashapp/stash/internal/identify.Options
IdentifyMetadataOptions:
model: github.com/stashapp/stash/internal/identify.MetadataOptions
IdentifyFieldOptions:
model: github.com/stashapp/stash/internal/identify.FieldOptions
IdentifyFieldStrategy:
model: github.com/stashapp/stash/internal/identify.FieldStrategy
ScraperSource:
model: github.com/stashapp/stash/pkg/scraper.Source
# rebind inputs to types
StashIDInput:
model: github.com/stashapp/stash/pkg/models.StashID
IdentifySourceInput:
model: github.com/stashapp/stash/internal/identify.Source
IdentifyFieldOptionsInput:
model: github.com/stashapp/stash/internal/identify.FieldOptions
IdentifyMetadataOptionsInput:
model: github.com/stashapp/stash/internal/identify.MetadataOptions
ScraperSourceInput:
model: github.com/stashapp/stash/pkg/scraper.Source

View File

@@ -5,8 +5,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
excludeImage
}
databasePath
backupDirectoryPath
generatedPath
metadataPath
scrapersPath
cachePath
calculateMD5
videoFileNamingAlgorithm
@@ -24,7 +26,6 @@ fragment ConfigGeneralData on ConfigGeneralResult {
username
password
maxSessionAge
trustedProxies
logFile
logOut
logLevel
@@ -44,6 +45,7 @@ fragment ConfigGeneralData on ConfigGeneralResult {
endpoint
api_key
}
pythonPath
}
fragment ConfigInterfaceData on ConfigInterfaceResult {
@@ -51,13 +53,34 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
soundOnPreview
wallShowTitle
wallPlayback
showScrubber
maximumLoopDuration
noBrowser
notificationsEnabled
autostartVideo
autostartVideoOnPlaySelected
continuePlaylistDefault
showStudioAsText
css
cssEnabled
javascript
javascriptEnabled
customLocales
customLocalesEnabled
language
slideshowDelay
imageLightbox {
slideshowDelay
displayMode
scaleUp
resetZoomOnNav
scrollMode
scrollAttemptsBeforeChange
}
disableDropdownCreate {
performer
tag
studio
}
handyKey
funscriptOffset
}
@@ -76,6 +99,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 +187,8 @@ fragment ConfigData on ConfigResult {
scraping {
...ConfigScrapingData
}
defaults {
...ConfigDefaultSettingsData
}
ui
}

View File

@@ -0,0 +1,46 @@
fragment FolderData on Folder {
id
path
}
fragment VideoFileData on VideoFile {
id
path
size
mod_time
duration
video_codec
audio_codec
width
height
frame_rate
bit_rate
fingerprints {
type
value
}
}
fragment ImageFileData on ImageFile {
id
path
size
mod_time
width
height
fingerprints {
type
value
}
}
fragment GalleryFileData on GalleryFile {
id
path
size
mod_time
fingerprints {
type
value
}
}

View File

@@ -1,19 +1,21 @@
fragment SlimGalleryData on Gallery {
id
checksum
path
title
date
url
details
rating
rating100
organized
files {
...GalleryFileData
}
folder {
...FolderData
}
image_count
cover {
file {
size
width
height
files {
...ImageFileData
}
paths {
@@ -37,8 +39,6 @@ fragment SlimGalleryData on Gallery {
image_path
}
scenes {
id
title
path
...SlimSceneData
}
}

View File

@@ -1,16 +1,21 @@
fragment GalleryData on Gallery {
id
checksum
path
created_at
updated_at
title
date
url
details
rating
rating100
organized
images {
...SlimImageData
files {
...GalleryFileData
}
folder {
...FolderData
}
cover {
...SlimImageData
}

View File

@@ -1,16 +1,12 @@
fragment SlimImageData on Image {
id
checksum
title
rating
rating100
organized
o_counter
path
file {
size
width
height
files {
...ImageFileData
}
paths {
@@ -20,8 +16,13 @@ fragment SlimImageData on Image {
galleries {
id
path
title
files {
path
}
folder {
path
}
}
studio {

View File

@@ -1,16 +1,14 @@
fragment ImageData on Image {
id
checksum
title
rating
rating100
organized
o_counter
path
created_at
updated_at
file {
size
width
height
files {
...ImageFileData
}
paths {

View File

@@ -2,4 +2,5 @@ fragment SlimMovieData on Movie {
id
name
front_image_path
rating100
}

View File

@@ -5,7 +5,7 @@ fragment MovieData on Movie {
aliases
duration
date
rating
rating100
director
studio {

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_cm
fake_tits
career_length
tattoos
piercings
tags {
id
name
@@ -12,5 +26,7 @@ fragment SlimPerformerData on Performer {
endpoint
stash_id
}
rating
rating100
death_date
weight
}

View File

@@ -10,7 +10,7 @@ fragment PerformerData on Performer {
ethnicity
country
eye_color
height
height_cm
measurements
fake_tits
career_length
@@ -18,6 +18,7 @@ fragment PerformerData on Performer {
piercings
aliases
favorite
ignore_auto_tag
image_path
scene_count
image_count
@@ -32,7 +33,7 @@ fragment PerformerData on Performer {
stash_id
endpoint
}
rating
rating100
details
death_date
hair_color

View File

@@ -1,27 +1,22 @@
fragment SlimSceneData on Scene {
id
checksum
oshash
title
code
details
director
url
date
rating
rating100
o_counter
organized
path
phash
interactive
interactive_speed
resume_time
play_duration
play_count
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
files {
...VideoFileData
}
paths {
@@ -30,20 +25,27 @@ fragment SlimSceneData on Scene {
stream
webp
vtt
chapters_vtt
sprite
funscript
interactive_heatmap
caption
}
scene_markers {
id
title
seconds
primary_tag {
id
name
}
}
galleries {
id
path
files {
path
}
title
}

View File

@@ -1,27 +1,29 @@
fragment SceneData on Scene {
id
checksum
oshash
title
code
details
director
url
date
rating
rating100
o_counter
organized
path
phash
interactive
interactive_speed
captions {
language_code
caption_type
}
created_at
updated_at
resume_time
last_played_at
play_duration
play_count
file {
size
duration
video_codec
audio_codec
width
height
framerate
bitrate
files {
...VideoFileData
}
paths {
@@ -30,9 +32,10 @@ fragment SceneData on Scene {
stream
webp
vtt
chapters_vtt
sprite
funscript
interactive_heatmap
caption
}
scene_markers {
@@ -66,4 +69,10 @@ fragment SceneData on Scene {
endpoint
stash_id
}
sceneStreams {
url
mime_type
label
}
}

View File

@@ -105,7 +105,9 @@ fragment ScrapedSceneTagData on ScrapedTag {
fragment ScrapedSceneData on ScrapedScene {
title
code
details
director
url
date
image
@@ -166,7 +168,9 @@ fragment ScrapedGalleryData on ScrapedGallery {
fragment ScrapedStashBoxSceneData on ScrapedScene {
title
code
details
director
url
date
image

View File

@@ -10,6 +10,6 @@ fragment SlimStudioData on Studio {
id
}
details
rating
rating100
aliases
}

View File

@@ -14,6 +14,7 @@ fragment StudioData on Studio {
name
image_path
}
ignore_auto_tag
image_path
scene_count
image_count
@@ -24,6 +25,6 @@ fragment StudioData on Studio {
endpoint
}
details
rating
rating100
aliases
}

View File

@@ -1,7 +1,9 @@
fragment TagData on Tag {
id
name
description
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

@@ -0,0 +1,3 @@
mutation DeleteFiles($ids: [ID!]!) {
deleteFiles(ids: $ids)
}

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

@@ -1,3 +1,11 @@
mutation SceneCreate(
$input: SceneCreateInput!) {
sceneCreate(input: $input) {
...SceneData
}
}
mutation SceneUpdate(
$input: SceneUpdateInput!) {
@@ -20,6 +28,14 @@ mutation ScenesUpdate($input : [SceneUpdateInput!]!) {
}
}
mutation SceneSaveActivity($id: ID!, $resume_time: Float, $playDuration: Float) {
sceneSaveActivity(id: $id, resume_time: $resume_time, playDuration: $playDuration)
}
mutation SceneIncrementPlayCount($id: ID!) {
sceneIncrementPlayCount(id: $id)
}
mutation SceneIncrementO($id: ID!) {
sceneIncrementO(id: $id)
}
@@ -43,3 +59,13 @@ mutation ScenesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated :
mutation SceneGenerateScreenshot($id: ID!, $at: Float) {
sceneGenerateScreenshot(id: $id, at: $at)
}
mutation SceneAssignFile($input: AssignSceneFileInput!) {
sceneAssignFile(input: $input)
}
mutation SceneMerge($input: SceneMergeInput!) {
sceneMerge(input: $input) {
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,6 +1,8 @@
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
}
@@ -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
@@ -46,7 +52,9 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
...SlimSceneData
}
title
code
details
director
url
date
rating
@@ -62,9 +70,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!
}
@@ -158,7 +162,9 @@ type Mutation {
setup(input: SetupInput!): Boolean!
migrate(input: MigrateInput!): Boolean!
sceneCreate(input: SceneCreateInput!): Scene
sceneUpdate(input: SceneUpdateInput!): Scene
sceneMerge(input: SceneMergeInput!): Scene
bulkSceneUpdate(input: BulkSceneUpdateInput!): [Scene!]
sceneDestroy(input: SceneDestroyInput!): Boolean!
scenesDestroy(input: ScenesDestroyInput!): Boolean!
@@ -171,6 +177,12 @@ type Mutation {
"""Resets the o-counter for a scene to 0. Returns the new value"""
sceneResetO(id: ID!): Int!
"""Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"""
sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!
"""Increments the play count for the scene. Returns the new play count value."""
sceneIncrementPlayCount(id: ID!): Int!
"""Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"""
sceneGenerateScreenshot(id: ID!, at: Float): String!
@@ -178,6 +190,8 @@ type Mutation {
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
sceneMarkerDestroy(id: ID!): Boolean!
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
imageUpdate(input: ImageUpdateInput!): Image
bulkImageUpdate(input: BulkImageUpdateInput!): [Image!]
imageDestroy(input: ImageDestroyInput!): Boolean!
@@ -215,6 +229,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
@@ -222,6 +237,8 @@ type Mutation {
tagsDestroy(ids: [ID!]!): Boolean!
tagsMerge(input: TagsMergeInput!): Tag
deleteFiles(ids: [ID!]!): Boolean!
# Saved filters
saveFilter(input: SaveFilterInput!): SavedFilter!
destroySavedFilter(input: DestroyFilterInput!): Boolean!
@@ -232,6 +249,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 +277,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 +295,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

@@ -37,20 +37,24 @@ input ConfigGeneralInput {
stashes: [StashConfigInput!]
"""Path to the SQLite database"""
databasePath: String
"""Path to backup directory"""
backupDirectoryPath: String
"""Path to generated files"""
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 +78,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 +108,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 {
@@ -112,6 +118,8 @@ type ConfigGeneralResult {
stashes: [StashConfig!]!
"""Path to the SQLite database"""
databasePath: String!
"""Path to backup directory"""
backupDirectoryPath: String!
"""Path to generated files"""
generatedPath: String!
"""Path to import/export files"""
@@ -155,7 +163,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 +194,166 @@ 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
"""Custom Javascript"""
javascript: String
javascriptEnabled: Boolean
"""Custom Locales"""
customLocales: String
customLocalesEnabled: 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
"""Custom Javascript"""
javascript: String
javascriptEnabled: Boolean
"""Custom Locales"""
customLocales: String
customLocalesEnabled: 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 +386,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 +402,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 +459,8 @@ type StashConfig {
input GenerateAPIKeyInput {
clear: Boolean
}
type StashBoxValidationResult {
valid: Boolean!
status: String!
}

View File

@@ -0,0 +1,97 @@
type Fingerprint {
type: String!
value: String!
}
type Folder {
id: ID!
path: String!
parent_folder_id: ID
zip_file_id: ID
mod_time: Time!
created_at: Time!
updated_at: Time!
}
interface BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprints: [Fingerprint!]!
created_at: Time!
updated_at: Time!
}
type VideoFile implements BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprints: [Fingerprint!]!
format: String!
width: Int!
height: Int!
duration: Float!
video_codec: String!
audio_codec: String!
frame_rate: Float!
bit_rate: Int!
created_at: Time!
updated_at: Time!
}
type ImageFile implements BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprints: [Fingerprint!]!
width: Int!
height: Int!
created_at: Time!
updated_at: Time!
}
type GalleryFile implements BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
mod_time: Time!
size: Int64!
fingerprints: [Fingerprint!]!
created_at: Time!
updated_at: Time!
}

View File

@@ -33,6 +33,20 @@ input ResolutionCriterionInput {
modifier: CriterionModifier!
}
input PHashDuplicationCriterionInput {
duplicated: Boolean
"""Currently unimplemented"""
distance: Int
}
input StashIDCriterionInput {
"""If present, this value is treated as a predicate.
That is, it will filter based on stash_ids with the matching endpoint"""
endpoint: String
stash_id: String
modifier: CriterionModifier!
}
input PerformerFilterType {
AND: PerformerFilterType
OR: PerformerFilterType
@@ -54,7 +68,9 @@ input PerformerFilterType {
"""Filter by eye color"""
eye_color: StringCriterionInput
"""Filter by height"""
height: StringCriterionInput
height: StringCriterionInput @deprecated(reason: "Use height_cm instead")
"""Filter by height in cm"""
height_cm: IntCriterionInput
"""Filter by measurements"""
measurements: StringCriterionInput
"""Filter by fake tits value"""
@@ -82,9 +98,13 @@ input PerformerFilterType {
"""Filter by gallery count"""
gallery_count: IntCriterionInput
"""Filter by StashID"""
stash_id: StringCriterionInput
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter by url"""
url: StringCriterionInput
"""Filter by hair color"""
@@ -95,6 +115,16 @@ 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
"""Filter by birthdate"""
birthdate: DateCriterionInput
"""Filter by death date"""
death_date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input SceneMarkerFilterType {
@@ -106,6 +136,16 @@ input SceneMarkerFilterType {
scene_tags: HierarchicalMultiCriterionInput
"""Filter to only include scene markers with these performers"""
performers: MultiCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
"""Filter by scene date"""
scene_date: DateCriterionInput
"""Filter by cscene reation time"""
scene_created_at: TimestampCriterionInput
"""Filter by lscene ast update time"""
scene_updated_at: TimestampCriterionInput
}
input SceneFilterType {
@@ -113,8 +153,11 @@ input SceneFilterType {
OR: SceneFilterType
NOT: SceneFilterType
id: IntCriterionInput
title: StringCriterionInput
code: StringCriterionInput
details: StringCriterionInput
director: StringCriterionInput
"""Filter by file oshash"""
oshash: StringCriterionInput
@@ -124,12 +167,18 @@ input SceneFilterType {
phash: StringCriterionInput
"""Filter by path"""
path: StringCriterionInput
"""Filter by file count"""
file_count: IntCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter by organized"""
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,16 +197,38 @@ 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"""
performer_count: IntCriterionInput
"""Filter by StashID"""
stash_id: StringCriterionInput
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter by url"""
url: StringCriterionInput
"""Filter by interactive"""
interactive: Boolean
"""Filter by InteractiveSpeed"""
interactive_speed: IntCriterionInput
"""Filter by captions"""
captions: StringCriterionInput
"""Filter by resume time"""
resume_time: IntCriterionInput
"""Filter by play count"""
play_count: IntCriterionInput
"""Filter by play duration (in seconds)"""
play_duration: IntCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input MovieFilterType {
@@ -169,7 +240,9 @@ input MovieFilterType {
"""Filter by duration (in seconds)"""
duration: IntCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter to only include movies with this studio"""
studios: HierarchicalMultiCriterionInput
"""Filter to only include movies missing this property"""
@@ -178,6 +251,12 @@ input MovieFilterType {
url: StringCriterionInput
"""Filter to only include movies where performer appears in a scene"""
performers: MultiCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input StudioFilterType {
@@ -190,11 +269,15 @@ input StudioFilterType {
"""Filter to only include studios with this parent studio"""
parents: MultiCriterionInput
"""Filter by StashID"""
stash_id: StringCriterionInput
stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter to only include studios missing this property"""
is_missing: String
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter by scene count"""
scene_count: IntCriterionInput
"""Filter by image count"""
@@ -205,6 +288,12 @@ input StudioFilterType {
url: StringCriterionInput
"""Filter by studio aliases"""
aliases: StringCriterionInput
"""Filter by autotag ignore value"""
ignore_auto_tag: Boolean
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input GalleryFilterType {
@@ -212,6 +301,7 @@ input GalleryFilterType {
OR: GalleryFilterType
NOT: GalleryFilterType
id: IntCriterionInput
title: StringCriterionInput
details: StringCriterionInput
@@ -219,12 +309,16 @@ input GalleryFilterType {
checksum: StringCriterionInput
"""Filter by path"""
path: StringCriterionInput
"""Filter by zip-file count"""
file_count: IntCriterionInput
"""Filter to only include galleries missing this property"""
is_missing: String
"""Filter to include/exclude galleries that were created from zip"""
is_zip: Boolean
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter by organized"""
organized: Boolean
"""Filter by average image resolution"""
@@ -241,10 +335,20 @@ 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"""
url: StringCriterionInput
"""Filter by date"""
date: DateCriterionInput
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input TagFilterType {
@@ -258,6 +362,9 @@ input TagFilterType {
"""Filter by tag aliases"""
aliases: StringCriterionInput
"""Filter by tag description"""
description: StringCriterionInput
"""Filter to only include tags missing this property"""
is_missing: String
@@ -287,6 +394,15 @@ input TagFilterType {
"""Filter by number f child tags the tag has"""
child_count: IntCriterionInput
"""Filter by autotag ignore value"""
ignore_auto_tag: Boolean
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
input ImageFilterType {
@@ -296,12 +412,18 @@ input ImageFilterType {
title: StringCriterionInput
""" Filter by image id"""
id: IntCriterionInput
"""Filter by file checksum"""
checksum: StringCriterionInput
"""Filter by path"""
path: StringCriterionInput
"""Filter by file count"""
file_count: IntCriterionInput
"""Filter by rating"""
rating: IntCriterionInput
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"""Filter by organized"""
organized: Boolean
"""Filter by o-counter"""
@@ -322,8 +444,14 @@ 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
"""Filter by creation time"""
created_at: TimestampCriterionInput
"""Filter by last update time"""
updated_at: TimestampCriterionInput
}
enum CriterionModifier {
@@ -380,6 +508,18 @@ input HierarchicalMultiCriterionInput {
depth: Int
}
input DateCriterionInput {
value: String!
value2: String
modifier: CriterionModifier!
}
input TimestampCriterionInput {
value: String!
value2: String
modifier: CriterionModifier!
}
enum FilterMode {
SCENES,
PERFORMERS,

View File

@@ -1,17 +1,23 @@
"""Gallery type"""
type Gallery {
id: ID!
checksum: String!
path: String
checksum: String! @deprecated(reason: "Use files.fingerprints")
path: String @deprecated(reason: "Use files.path")
title: String
url: String
date: String
details: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean!
created_at: Time!
updated_at: Time!
file_mod_time: Time
file_mod_time: Time @deprecated(reason: "Use files.mod_time")
files: [GalleryFile!]!
folder: Folder
scenes: [Scene!]!
studio: Studio
@@ -20,22 +26,19 @@ type Gallery {
performers: [Performer!]!
"""The images in the gallery"""
images: [Image!]! # Resolver
images: [Image!]! @deprecated(reason: "Use findImages")
cover: Image
}
type GalleryFilesType {
index: Int!
name: String
path: String
}
input GalleryCreateInput {
title: String!
url: String
date: String
details: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
scene_ids: [ID!]
studio_id: ID
@@ -50,12 +53,17 @@ input GalleryUpdateInput {
url: String
date: String
details: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
scene_ids: [ID!]
studio_id: ID
tag_ids: [ID!]
performer_ids: [ID!]
primary_file_id: ID
}
input BulkGalleryUpdateInput {
@@ -64,7 +72,10 @@ input BulkGalleryUpdateInput {
url: String
date: String
details: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
scene_ids: BulkUpdateIds
studio_id: ID
@@ -74,6 +85,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

@@ -1,16 +1,21 @@
type Image {
id: ID!
checksum: String
checksum: String @deprecated(reason: "Use files.fingerprints")
title: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
o_counter: Int
organized: Boolean!
path: String!
path: String! @deprecated(reason: "Use files.path")
created_at: Time!
updated_at: Time!
file_mod_time: Time
file_mod_time: Time @deprecated(reason: "Use files.mod_time")
file: ImageFileType! # Resolver
file: ImageFileType! @deprecated(reason: "Use files.mod_time")
files: [ImageFile!]!
paths: ImagePathsType! # Resolver
galleries: [Gallery!]!
@@ -20,9 +25,10 @@ type Image {
}
type ImageFileType {
size: Int
width: Int
height: Int
mod_time: Time!
size: Int!
width: Int!
height: Int!
}
type ImagePathsType {
@@ -34,20 +40,28 @@ input ImageUpdateInput {
clientMutationId: String
id: ID!
title: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
performer_ids: [ID!]
tag_ids: [ID!]
gallery_ids: [ID!]
primary_file_id: ID
}
input BulkImageUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
@@ -70,5 +84,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,12 +36,54 @@ 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!]
# useFileMetadata is deprecated with the new file management system
# if this functionality is desired, then we can make a built in scraper instead.
"""Set name, date, details from metadata (if present)"""
useFileMetadata: Boolean
useFileMetadata: Boolean @deprecated(reason: "Not implemented")
# stripFileExtension is deprecated since we no longer set the title from the
# filename - it is automatically returned if the object has no title. If this
# functionality is desired, then we could make this an option to not include
# the extension in the auto-generated title.
"""Strip file extension from title"""
stripFileExtension: Boolean
stripFileExtension: Boolean @deprecated(reason: "Not implemented")
"""Generate previews during scan"""
scanGeneratePreviews: Boolean
"""Generate image previews during scan"""
@@ -49,9 +94,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 +134,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

@@ -6,7 +6,10 @@ type Movie {
"""Duration in seconds"""
duration: Int
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio: Studio
director: String
synopsis: String
@@ -26,7 +29,10 @@ input MovieCreateInput {
"""Duration in seconds"""
duration: Int
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
synopsis: String
@@ -43,7 +49,10 @@ input MovieUpdateInput {
aliases: String
duration: Int
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
synopsis: String
@@ -54,6 +63,17 @@ input MovieUpdateInput {
back_image: String
}
input BulkMovieUpdateInput {
clientMutationId: String
ids: [ID!]
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
}
input MovieDestroyInput {
id: ID!
}

View File

@@ -19,7 +19,8 @@ type Performer {
ethnicity: String
country: String
eye_color: String
height: String
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
career_length: String
@@ -28,6 +29,7 @@ type Performer {
aliases: String
favorite: Boolean!
tags: [Tag!]!
ignore_auto_tag: Boolean!
image_path: String # Resolver
scene_count: Int # Resolver
@@ -35,7 +37,10 @@ type Performer {
gallery_count: Int # Resolver
scenes: [Scene!]!
stash_ids: [StashID!]!
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
death_date: String
hair_color: String
@@ -54,7 +59,9 @@ input PerformerCreateInput {
ethnicity: String
country: String
eye_color: String
height: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
career_length: String
@@ -68,11 +75,15 @@ input PerformerCreateInput {
"""This should be a URL or a base64 encoded data URL"""
image: String
stash_ids: [StashIDInput!]
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input PerformerUpdateInput {
@@ -84,7 +95,9 @@ input PerformerUpdateInput {
ethnicity: String
country: String
eye_color: String
height: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
career_length: String
@@ -98,11 +111,15 @@ input PerformerUpdateInput {
"""This should be a URL or a base64 encoded data URL"""
image: String
stash_ids: [StashIDInput!]
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input BulkPerformerUpdateInput {
@@ -114,7 +131,9 @@ input BulkPerformerUpdateInput {
ethnicity: String
country: String
eye_color: String
height: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
career_length: String
@@ -125,11 +144,15 @@ input BulkPerformerUpdateInput {
instagram: String
favorite: Boolean
tag_ids: BulkUpdateIds
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
death_date: String
hair_color: String
weight: Int
ignore_auto_tag: Boolean
}
input PerformerDestroyInput {

View File

@@ -0,0 +1,14 @@
"""
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
scalar Int64

View File

@@ -15,9 +15,11 @@ type ScenePathsType {
stream: String # Resolver
webp: String # Resolver
vtt: String # Resolver
chapters_vtt: String # Resolver
chapters_vtt: String @deprecated
sprite: String # Resolver
funscript: String # Resolver
interactive_heatmap: String # Resolver
caption: String # Resolver
}
type SceneMovie {
@@ -25,25 +27,46 @@ type SceneMovie {
scene_index: Int
}
type VideoCaption {
language_code: String!
caption_type: String!
}
type Scene {
id: ID!
checksum: String
oshash: String
checksum: String @deprecated(reason: "Use files.fingerprints")
oshash: String @deprecated(reason: "Use files.fingerprints")
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean!
o_counter: Int
path: String!
phash: String
path: String! @deprecated(reason: "Use files.path")
phash: String @deprecated(reason: "Use files.fingerprints")
interactive: Boolean!
interactive_speed: Int
captions: [VideoCaption!]
created_at: Time!
updated_at: Time!
file_mod_time: Time
"""The last time play count was updated"""
last_played_at: Time
"""The time index a scene was left at"""
resume_time: Float
"""The total time a scene has spent playing"""
play_duration: Float
"""The number ot times a scene has been played"""
play_count: Int
file: SceneFileType! # Resolver
file: SceneFileType! @deprecated(reason: "Use files")
files: [VideoFile!]!
paths: ScenePathsType! # Resolver
scene_markers: [SceneMarker!]!
@@ -53,6 +76,9 @@ type Scene {
tags: [Tag!]!
performers: [Performer!]!
stash_ids: [StashID!]!
"""Return valid stream paths"""
sceneStreams: [SceneStreamEndpoint!]!
}
input SceneMovieInput {
@@ -60,14 +86,17 @@ input SceneMovieInput {
scene_index: Int
}
input SceneUpdateInput {
clientMutationId: String
id: ID!
input SceneCreateInput {
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
@@ -77,6 +106,44 @@ input SceneUpdateInput {
"""This should be a URL or a base64 encoded data URL"""
cover_image: String
stash_ids: [StashIDInput!]
"""The first id will be assigned as primary. Files will be reassigned from
existing scenes if applicable. Files must not already be primary for another scene"""
file_ids: [ID!]
}
input SceneUpdateInput {
clientMutationId: String
id: ID!
title: String
code: String
details: String
director: String
url: String
date: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
o_counter: Int
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [SceneMovieInput!]
tag_ids: [ID!]
"""This should be a URL or a base64 encoded data URL"""
cover_image: String
stash_ids: [StashIDInput!]
"""The time index a scene was left at"""
resume_time: Float
"""The total time a scene has spent playing"""
play_duration: Float
"""The number ot times a scene has been played"""
play_count: Int
primary_file_id: ID
}
enum BulkUpdateIdMode {
@@ -94,10 +161,15 @@ input BulkSceneUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
gallery_ids: BulkUpdateIds
@@ -120,6 +192,10 @@ input ScenesDestroyInput {
type FindScenesResultType {
count: Int!
"""Total duration in seconds"""
duration: Float!
"""Total file size in bytes"""
filesize: Float!
scenes: [Scene!]!
}
@@ -138,10 +214,15 @@ type SceneMovieID {
type SceneParserResult {
scene: Scene!
title: String
code: String
details: String
director: String
url: String
date: String
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
@@ -164,3 +245,17 @@ type SceneStreamEndpoint {
mime_type: String
label: String
}
input AssignSceneFileInput {
scene_id: ID!
file_id: ID!
}
input SceneMergeInput {
"""If destination scene has no files, then the primary file of the
first source scene will be assigned as primary"""
source: [ID!]!
destination: ID!
# values defined here will override values in the destination
values: SceneUpdateInput
}

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
}
@@ -43,7 +61,9 @@ type ScrapedTag {
type ScrapedScene {
title: String
code: String
details: String
director: String
url: String
date: String
@@ -64,7 +84,9 @@ type ScrapedScene {
input ScrapedSceneInput {
title: String
code: String
details: String
director: String
url: String
date: String
@@ -95,7 +117,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 +208,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,13 +6,17 @@ type Studio {
parent_studio: Studio
child_studios: [Studio!]!
aliases: [String!]!
ignore_auto_tag: Boolean!
image_path: String # Resolver
scene_count: Int # Resolver
image_count: Int # Resolver
gallery_count: Int # Resolver
stash_ids: [StashID!]!
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
created_at: Time!
updated_at: Time!
@@ -27,9 +31,13 @@ input StudioCreateInput {
"""This should be a URL or a base64 encoded data URL"""
image: String
stash_ids: [StashIDInput!]
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
aliases: [String!]
ignore_auto_tag: Boolean
}
input StudioUpdateInput {
@@ -40,9 +48,13 @@ input StudioUpdateInput {
"""This should be a URL or a base64 encoded data URL"""
image: String
stash_ids: [StashIDInput!]
rating: Int
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
aliases: [String!]
ignore_auto_tag: Boolean
}
input StudioDestroyInput {

View File

@@ -1,7 +1,9 @@
type Tag {
id: ID!
name: String!
description: String
aliases: [String!]!
ignore_auto_tag: Boolean!
created_at: Time!
updated_at: Time!
@@ -18,7 +20,9 @@ type Tag {
input TagCreateInput {
name: String!
description: String
aliases: [String!]
ignore_auto_tag: Boolean
"""This should be a URL or a base64 encoded data URL"""
image: String
@@ -30,7 +34,9 @@ input TagCreateInput {
input TagUpdateInput {
id: ID!
name: String
description: 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 {
@@ -98,7 +94,9 @@ fragment FingerprintFragment on Fingerprint {
fragment SceneFragment on Scene {
id
title
code
details
director
duration
date
urls {
@@ -127,8 +125,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 +155,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

@@ -0,0 +1,306 @@
package api
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/models"
)
const updateInputField = "input"
func getArgumentMap(ctx context.Context) map[string]interface{} {
rctx := graphql.GetFieldContext(ctx)
reqCtx := graphql.GetOperationContext(ctx)
return rctx.Field.ArgumentMap(reqCtx.Variables)
}
func getUpdateInputMap(ctx context.Context) map[string]interface{} {
return getNamedUpdateInputMap(ctx, updateInputField)
}
func getNamedUpdateInputMap(ctx context.Context, field string) map[string]interface{} {
args := getArgumentMap(ctx)
// field can be qualified
fields := strings.Split(field, ".")
currArgs := args
for _, f := range fields {
v, found := currArgs[f]
if !found {
currArgs = nil
break
}
currArgs, _ = v.(map[string]interface{})
if currArgs == nil {
break
}
}
if currArgs != nil {
return currArgs
}
return make(map[string]interface{})
}
func getUpdateInputMaps(ctx context.Context) []map[string]interface{} {
args := getArgumentMap(ctx)
input := args[updateInputField]
var ret []map[string]interface{}
if input != nil {
// convert []interface{} into []map[string]interface{}
iSlice, _ := input.([]interface{})
for _, i := range iSlice {
m, _ := i.(map[string]interface{})
if m != nil {
ret = append(ret, m)
}
}
}
return ret
}
type changesetTranslator struct {
inputMap map[string]interface{}
}
func (t changesetTranslator) hasField(field string) bool {
if t.inputMap == nil {
return false
}
_, found := t.inputMap[field]
return found
}
func (t changesetTranslator) getFields() []string {
var ret []string
for k := range t.inputMap {
ret = append(ret, k)
}
return ret
}
func (t changesetTranslator) nullString(value *string, field string) *sql.NullString {
if !t.hasField(field) {
return nil
}
ret := &sql.NullString{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) string(value *string, field string) string {
if value == nil {
return ""
}
return *value
}
func (t changesetTranslator) optionalString(value *string, field string) models.OptionalString {
if !t.hasField(field) {
return models.OptionalString{}
}
return models.NewOptionalStringPtr(value)
}
func (t changesetTranslator) sqliteDate(value *string, field string) *models.SQLiteDate {
if !t.hasField(field) {
return nil
}
ret := &models.SQLiteDate{}
if value != nil {
ret.String = *value
ret.Valid = true
}
return ret
}
func (t changesetTranslator) optionalDate(value *string, field string) models.OptionalDate {
if !t.hasField(field) {
return models.OptionalDate{}
}
if value == nil {
return models.OptionalDate{
Set: true,
Null: true,
}
}
return models.NewOptionalDate(models.NewDate(*value))
}
func (t changesetTranslator) datePtr(value *string, field string) *models.Date {
if value == nil {
return nil
}
d := models.NewDate(*value)
return &d
}
func (t changesetTranslator) intPtrFromString(value *string, field string) (*int, error) {
if value == nil || *value == "" {
return nil, nil
}
vv, err := strconv.Atoi(*value)
if err != nil {
return nil, fmt.Errorf("converting %v to int: %w", *value, err)
}
return &vv, nil
}
func (t changesetTranslator) nullInt64(value *int, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64 = int64(*value)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) ratingConversion(legacyValue *int, rating100Value *int) *sql.NullInt64 {
const (
legacyField = "rating"
rating100Field = "rating100"
)
legacyRating := t.nullInt64(legacyValue, legacyField)
if legacyRating != nil {
if legacyRating.Valid {
legacyRating.Int64 = int64(models.Rating5To100(int(legacyRating.Int64)))
}
return legacyRating
}
return t.nullInt64(rating100Value, rating100Field)
}
func (t changesetTranslator) ratingConversionInt(legacyValue *int, rating100Value *int) *int {
const (
legacyField = "rating"
rating100Field = "rating100"
)
legacyRating := t.optionalInt(legacyValue, legacyField)
if legacyRating.Set && !(legacyRating.Null) {
ret := int(models.Rating5To100(int(legacyRating.Value)))
return &ret
}
o := t.optionalInt(rating100Value, rating100Field)
if o.Set && !(o.Null) {
return &o.Value
}
return nil
}
func (t changesetTranslator) ratingConversionOptional(legacyValue *int, rating100Value *int) models.OptionalInt {
const (
legacyField = "rating"
rating100Field = "rating100"
)
legacyRating := t.optionalInt(legacyValue, legacyField)
if legacyRating.Set && !(legacyRating.Null) {
legacyRating.Value = int(models.Rating5To100(int(legacyRating.Value)))
return legacyRating
}
return t.optionalInt(rating100Value, rating100Field)
}
func (t changesetTranslator) optionalInt(value *int, field string) models.OptionalInt {
if !t.hasField(field) {
return models.OptionalInt{}
}
return models.NewOptionalIntPtr(value)
}
func (t changesetTranslator) nullInt64FromString(value *string, field string) *sql.NullInt64 {
if !t.hasField(field) {
return nil
}
ret := &sql.NullInt64{}
if value != nil {
ret.Int64, _ = strconv.ParseInt(*value, 10, 64)
ret.Valid = true
}
return ret
}
func (t changesetTranslator) optionalIntFromString(value *string, field string) (models.OptionalInt, error) {
if !t.hasField(field) {
return models.OptionalInt{}, nil
}
if value == nil {
return models.OptionalInt{
Set: true,
Null: true,
}, nil
}
vv, err := strconv.Atoi(*value)
if err != nil {
return models.OptionalInt{}, fmt.Errorf("converting %v to int: %w", *value, err)
}
return models.NewOptionalInt(vv), nil
}
func (t changesetTranslator) bool(value *bool, field string) bool {
if value == nil {
return false
}
return *value
}
func (t changesetTranslator) optionalBool(value *bool, field string) models.OptionalBool {
if !t.hasField(field) {
return models.OptionalBool{}
}
return models.NewOptionalBoolPtr(value)
}
func (t changesetTranslator) optionalFloat64(value *float64, field string) models.OptionalFloat64 {
if !t.hasField(field) {
return models.OptionalFloat64{}
}
return models.NewOptionalFloat64Ptr(value)
}

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,11 @@ 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"
"github.com/stashapp/stash/pkg/models"
)
type imageBox struct {
@@ -17,14 +18,35 @@ 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 err != nil {
return err
}
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
@@ -65,7 +87,7 @@ func initialiseCustomImages() {
}
}
func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte, error) {
func getRandomPerformerImageUsingName(name string, gender models.GenderEnum, customPath string) ([]byte, error) {
var box *imageBox
// If we have a custom path, we should return a new box in the given path.
@@ -74,10 +96,10 @@ func getRandomPerformerImageUsingName(name, gender, customPath string) ([]byte,
}
if box == nil {
switch strings.ToUpper(gender) {
case "FEMALE":
switch gender {
case models.GenderEnumFemale:
box = performerBox
case "MALE":
case models.GenderEnumMale:
box = performerBoxMale
default:
box = performerBox
@@ -85,7 +107,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

View File

@@ -0,0 +1,261 @@
//go:generate go run -mod=vendor github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
//go:generate go run -mod=vendor github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
//go:generate go run -mod=vendor github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
//go:generate go run -mod=vendor github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
//go:generate go run -mod=vendor github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
//go:generate go run -mod=vendor github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
//go:generate go run -mod=vendor github.com/vektah/dataloaden MovieLoader int *github.com/stashapp/stash/pkg/models.Movie
//go:generate go run -mod=vendor github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/file.ID github.com/stashapp/stash/pkg/file.File
//go:generate go run -mod=vendor github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
//go:generate go run -mod=vendor github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
//go:generate go run -mod=vendor github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/file.ID
package loaders
import (
"context"
"net/http"
"time"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
type contextKey struct{ name string }
var (
loadersCtxKey = &contextKey{"loaders"}
)
const (
wait = 1 * time.Millisecond
maxBatch = 100
)
type Loaders struct {
SceneByID *SceneLoader
SceneFiles *SceneFileIDsLoader
ImageFiles *ImageFileIDsLoader
GalleryFiles *GalleryFileIDsLoader
GalleryByID *GalleryLoader
ImageByID *ImageLoader
PerformerByID *PerformerLoader
StudioByID *StudioLoader
TagByID *TagLoader
MovieByID *MovieLoader
FileByID *FileLoader
}
type Middleware struct {
DatabaseProvider txn.DatabaseProvider
Repository manager.Repository
}
func (m Middleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ldrs := Loaders{
SceneByID: &SceneLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenes(ctx),
},
GalleryByID: &GalleryLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchGalleries(ctx),
},
ImageByID: &ImageLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchImages(ctx),
},
PerformerByID: &PerformerLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchPerformers(ctx),
},
StudioByID: &StudioLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchStudios(ctx),
},
TagByID: &TagLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchTags(ctx),
},
MovieByID: &MovieLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchMovies(ctx),
},
FileByID: &FileLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchFiles(ctx),
},
SceneFiles: &SceneFileIDsLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesFileIDs(ctx),
},
ImageFiles: &ImageFileIDsLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchImagesFileIDs(ctx),
},
GalleryFiles: &GalleryFileIDsLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchGalleriesFileIDs(ctx),
},
}
newCtx := context.WithValue(r.Context(), loadersCtxKey, ldrs)
next.ServeHTTP(w, r.WithContext(newCtx))
})
}
func From(ctx context.Context) Loaders {
return ctx.Value(loadersCtxKey).(Loaders)
}
func toErrorSlice(err error) []error {
if err != nil {
return []error{err}
}
return nil
}
func (m Middleware) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithDatabase(ctx, m.DatabaseProvider, fn)
}
func (m Middleware) fetchScenes(ctx context.Context) func(keys []int) ([]*models.Scene, []error) {
return func(keys []int) (ret []*models.Scene, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchImages(ctx context.Context) func(keys []int) ([]*models.Image, []error) {
return func(keys []int) (ret []*models.Image, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchGalleries(ctx context.Context) func(keys []int) ([]*models.Gallery, []error) {
return func(keys []int) (ret []*models.Gallery, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchPerformers(ctx context.Context) func(keys []int) ([]*models.Performer, []error) {
return func(keys []int) (ret []*models.Performer, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Performer.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*models.Studio, []error) {
return func(keys []int) (ret []*models.Studio, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Studio.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchTags(ctx context.Context) func(keys []int) ([]*models.Tag, []error) {
return func(keys []int) (ret []*models.Tag, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Tag.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models.Movie, []error) {
return func(keys []int) (ret []*models.Movie, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Movie.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchFiles(ctx context.Context) func(keys []file.ID) ([]file.File, []error) {
return func(keys []file.ID) (ret []file.File, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.File.Find(ctx, keys...)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyFileIDs(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.GetManyFileIDs(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchGalleriesFileIDs(ctx context.Context) func(keys []int) ([][]file.ID, []error) {
return func(keys []int) (ret [][]file.ID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.GetManyFileIDs(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}

View File

@@ -0,0 +1,221 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
)
// FileLoaderConfig captures the config to create a new FileLoader
type FileLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []file.ID) ([]file.File, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewFileLoader creates a new FileLoader given a fetch, wait, and maxBatch
func NewFileLoader(config FileLoaderConfig) *FileLoader {
return &FileLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// FileLoader batches and caches requests
type FileLoader struct {
// this method provides the data for the loader
fetch func(keys []file.ID) ([]file.File, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[file.ID]file.File
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *fileLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type fileLoaderBatch struct {
keys []file.ID
data []file.File
error []error
closing bool
done chan struct{}
}
// Load a File by key, batching and caching will be applied automatically
func (l *FileLoader) Load(key file.ID) (file.File, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a File.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *FileLoader) LoadThunk(key file.ID) func() (file.File, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (file.File, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &fileLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (file.File, error) {
<-batch.done
var data file.File
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *FileLoader) LoadAll(keys []file.ID) ([]file.File, []error) {
results := make([]func() (file.File, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
files := make([]file.File, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
files[i], errors[i] = thunk()
}
return files, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Files.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *FileLoader) LoadAllThunk(keys []file.ID) func() ([]file.File, []error) {
results := make([]func() (file.File, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]file.File, []error) {
files := make([]file.File, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
files[i], errors[i] = thunk()
}
return files, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *FileLoader) Prime(key file.ID, value file.File) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
l.unsafeSet(key, value)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *FileLoader) Clear(key file.ID) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *FileLoader) unsafeSet(key file.ID, value file.File) {
if l.cache == nil {
l.cache = map[file.ID]file.File{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *fileLoaderBatch) keyIndex(l *FileLoader, key file.ID) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *fileLoaderBatch) startTimer(l *FileLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *fileLoaderBatch) end(l *FileLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,225 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
)
// GalleryFileIDsLoaderConfig captures the config to create a new GalleryFileIDsLoader
type GalleryFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewGalleryFileIDsLoader creates a new GalleryFileIDsLoader given a fetch, wait, and maxBatch
func NewGalleryFileIDsLoader(config GalleryFileIDsLoaderConfig) *GalleryFileIDsLoader {
return &GalleryFileIDsLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// GalleryFileIDsLoader batches and caches requests
type GalleryFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int][]file.ID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *galleryFileIDsLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type galleryFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *GalleryFileIDsLoader) Load(key int) ([]file.ID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &galleryFileIDsLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
<-batch.done
var data []file.ID
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *GalleryFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *GalleryFileIDsLoader) Prime(key int, value []file.ID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *GalleryFileIDsLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *GalleryFileIDsLoader) unsafeSet(key int, value []file.ID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *galleryFileIDsLoaderBatch) keyIndex(l *GalleryFileIDsLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *galleryFileIDsLoaderBatch) startTimer(l *GalleryFileIDsLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *galleryFileIDsLoaderBatch) end(l *GalleryFileIDsLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// GalleryLoaderConfig captures the config to create a new GalleryLoader
type GalleryLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Gallery, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewGalleryLoader creates a new GalleryLoader given a fetch, wait, and maxBatch
func NewGalleryLoader(config GalleryLoaderConfig) *GalleryLoader {
return &GalleryLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// GalleryLoader batches and caches requests
type GalleryLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Gallery, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Gallery
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *galleryLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type galleryLoaderBatch struct {
keys []int
data []*models.Gallery
error []error
closing bool
done chan struct{}
}
// Load a Gallery by key, batching and caching will be applied automatically
func (l *GalleryLoader) Load(key int) (*models.Gallery, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Gallery.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryLoader) LoadThunk(key int) func() (*models.Gallery, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Gallery, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &galleryLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Gallery, error) {
<-batch.done
var data *models.Gallery
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *GalleryLoader) LoadAll(keys []int) ([]*models.Gallery, []error) {
results := make([]func() (*models.Gallery, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
gallerys := make([]*models.Gallery, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
gallerys[i], errors[i] = thunk()
}
return gallerys, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Gallerys.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *GalleryLoader) LoadAllThunk(keys []int) func() ([]*models.Gallery, []error) {
results := make([]func() (*models.Gallery, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Gallery, []error) {
gallerys := make([]*models.Gallery, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
gallerys[i], errors[i] = thunk()
}
return gallerys, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *GalleryLoader) Prime(key int, value *models.Gallery) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *GalleryLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *GalleryLoader) unsafeSet(key int, value *models.Gallery) {
if l.cache == nil {
l.cache = map[int]*models.Gallery{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *galleryLoaderBatch) keyIndex(l *GalleryLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *galleryLoaderBatch) startTimer(l *GalleryLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *galleryLoaderBatch) end(l *GalleryLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,225 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
)
// ImageFileIDsLoaderConfig captures the config to create a new ImageFileIDsLoader
type ImageFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewImageFileIDsLoader creates a new ImageFileIDsLoader given a fetch, wait, and maxBatch
func NewImageFileIDsLoader(config ImageFileIDsLoaderConfig) *ImageFileIDsLoader {
return &ImageFileIDsLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// ImageFileIDsLoader batches and caches requests
type ImageFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int][]file.ID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *imageFileIDsLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type imageFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *ImageFileIDsLoader) Load(key int) ([]file.ID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &imageFileIDsLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
<-batch.done
var data []file.ID
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *ImageFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *ImageFileIDsLoader) Prime(key int, value []file.ID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *ImageFileIDsLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *ImageFileIDsLoader) unsafeSet(key int, value []file.ID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *imageFileIDsLoaderBatch) keyIndex(l *ImageFileIDsLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *imageFileIDsLoaderBatch) startTimer(l *ImageFileIDsLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *imageFileIDsLoaderBatch) end(l *ImageFileIDsLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// ImageLoaderConfig captures the config to create a new ImageLoader
type ImageLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Image, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewImageLoader creates a new ImageLoader given a fetch, wait, and maxBatch
func NewImageLoader(config ImageLoaderConfig) *ImageLoader {
return &ImageLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// ImageLoader batches and caches requests
type ImageLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Image, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Image
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *imageLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type imageLoaderBatch struct {
keys []int
data []*models.Image
error []error
closing bool
done chan struct{}
}
// Load a Image by key, batching and caching will be applied automatically
func (l *ImageLoader) Load(key int) (*models.Image, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Image.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageLoader) LoadThunk(key int) func() (*models.Image, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Image, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &imageLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Image, error) {
<-batch.done
var data *models.Image
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *ImageLoader) LoadAll(keys []int) ([]*models.Image, []error) {
results := make([]func() (*models.Image, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
images := make([]*models.Image, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
images[i], errors[i] = thunk()
}
return images, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Images.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *ImageLoader) LoadAllThunk(keys []int) func() ([]*models.Image, []error) {
results := make([]func() (*models.Image, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Image, []error) {
images := make([]*models.Image, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
images[i], errors[i] = thunk()
}
return images, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *ImageLoader) Prime(key int, value *models.Image) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *ImageLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *ImageLoader) unsafeSet(key int, value *models.Image) {
if l.cache == nil {
l.cache = map[int]*models.Image{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *imageLoaderBatch) keyIndex(l *ImageLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *imageLoaderBatch) startTimer(l *ImageLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *imageLoaderBatch) end(l *ImageLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// MovieLoaderConfig captures the config to create a new MovieLoader
type MovieLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Movie, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewMovieLoader creates a new MovieLoader given a fetch, wait, and maxBatch
func NewMovieLoader(config MovieLoaderConfig) *MovieLoader {
return &MovieLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// MovieLoader batches and caches requests
type MovieLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Movie, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Movie
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *movieLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type movieLoaderBatch struct {
keys []int
data []*models.Movie
error []error
closing bool
done chan struct{}
}
// Load a Movie by key, batching and caching will be applied automatically
func (l *MovieLoader) Load(key int) (*models.Movie, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Movie.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *MovieLoader) LoadThunk(key int) func() (*models.Movie, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Movie, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &movieLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Movie, error) {
<-batch.done
var data *models.Movie
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *MovieLoader) LoadAll(keys []int) ([]*models.Movie, []error) {
results := make([]func() (*models.Movie, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
movies := make([]*models.Movie, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
movies[i], errors[i] = thunk()
}
return movies, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Movies.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *MovieLoader) LoadAllThunk(keys []int) func() ([]*models.Movie, []error) {
results := make([]func() (*models.Movie, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Movie, []error) {
movies := make([]*models.Movie, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
movies[i], errors[i] = thunk()
}
return movies, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *MovieLoader) Prime(key int, value *models.Movie) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *MovieLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *MovieLoader) unsafeSet(key int, value *models.Movie) {
if l.cache == nil {
l.cache = map[int]*models.Movie{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *movieLoaderBatch) keyIndex(l *MovieLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *movieLoaderBatch) startTimer(l *MovieLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *movieLoaderBatch) end(l *MovieLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// PerformerLoaderConfig captures the config to create a new PerformerLoader
type PerformerLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Performer, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewPerformerLoader creates a new PerformerLoader given a fetch, wait, and maxBatch
func NewPerformerLoader(config PerformerLoaderConfig) *PerformerLoader {
return &PerformerLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// PerformerLoader batches and caches requests
type PerformerLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Performer, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Performer
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *performerLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type performerLoaderBatch struct {
keys []int
data []*models.Performer
error []error
closing bool
done chan struct{}
}
// Load a Performer by key, batching and caching will be applied automatically
func (l *PerformerLoader) Load(key int) (*models.Performer, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Performer.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *PerformerLoader) LoadThunk(key int) func() (*models.Performer, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Performer, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &performerLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Performer, error) {
<-batch.done
var data *models.Performer
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *PerformerLoader) LoadAll(keys []int) ([]*models.Performer, []error) {
results := make([]func() (*models.Performer, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
performers := make([]*models.Performer, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
performers[i], errors[i] = thunk()
}
return performers, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Performers.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *PerformerLoader) LoadAllThunk(keys []int) func() ([]*models.Performer, []error) {
results := make([]func() (*models.Performer, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Performer, []error) {
performers := make([]*models.Performer, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
performers[i], errors[i] = thunk()
}
return performers, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *PerformerLoader) Prime(key int, value *models.Performer) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *PerformerLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *PerformerLoader) unsafeSet(key int, value *models.Performer) {
if l.cache == nil {
l.cache = map[int]*models.Performer{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *performerLoaderBatch) keyIndex(l *PerformerLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *performerLoaderBatch) startTimer(l *PerformerLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *performerLoaderBatch) end(l *PerformerLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,225 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/file"
)
// SceneFileIDsLoaderConfig captures the config to create a new SceneFileIDsLoader
type SceneFileIDsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]file.ID, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewSceneFileIDsLoader creates a new SceneFileIDsLoader given a fetch, wait, and maxBatch
func NewSceneFileIDsLoader(config SceneFileIDsLoaderConfig) *SceneFileIDsLoader {
return &SceneFileIDsLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// SceneFileIDsLoader batches and caches requests
type SceneFileIDsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]file.ID, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int][]file.ID
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *sceneFileIDsLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type sceneFileIDsLoaderBatch struct {
keys []int
data [][]file.ID
error []error
closing bool
done chan struct{}
}
// Load a ID by key, batching and caching will be applied automatically
func (l *SceneFileIDsLoader) Load(key int) ([]file.ID, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a ID.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneFileIDsLoader) LoadThunk(key int) func() ([]file.ID, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]file.ID, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &sceneFileIDsLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]file.ID, error) {
<-batch.done
var data []file.ID
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *SceneFileIDsLoader) LoadAll(keys []int) ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
// LoadAllThunk returns a function that when called will block waiting for a IDs.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneFileIDsLoader) LoadAllThunk(keys []int) func() ([][]file.ID, []error) {
results := make([]func() ([]file.ID, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]file.ID, []error) {
iDs := make([][]file.ID, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
iDs[i], errors[i] = thunk()
}
return iDs, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *SceneFileIDsLoader) Prime(key int, value []file.ID) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := make([]file.ID, len(value))
copy(cpy, value)
l.unsafeSet(key, cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *SceneFileIDsLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *SceneFileIDsLoader) unsafeSet(key int, value []file.ID) {
if l.cache == nil {
l.cache = map[int][]file.ID{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *sceneFileIDsLoaderBatch) keyIndex(l *SceneFileIDsLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *sceneFileIDsLoaderBatch) startTimer(l *SceneFileIDsLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *sceneFileIDsLoaderBatch) end(l *SceneFileIDsLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// SceneLoaderConfig captures the config to create a new SceneLoader
type SceneLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Scene, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewSceneLoader creates a new SceneLoader given a fetch, wait, and maxBatch
func NewSceneLoader(config SceneLoaderConfig) *SceneLoader {
return &SceneLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// SceneLoader batches and caches requests
type SceneLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Scene, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Scene
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *sceneLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type sceneLoaderBatch struct {
keys []int
data []*models.Scene
error []error
closing bool
done chan struct{}
}
// Load a Scene by key, batching and caching will be applied automatically
func (l *SceneLoader) Load(key int) (*models.Scene, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Scene.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneLoader) LoadThunk(key int) func() (*models.Scene, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Scene, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &sceneLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Scene, error) {
<-batch.done
var data *models.Scene
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *SceneLoader) LoadAll(keys []int) ([]*models.Scene, []error) {
results := make([]func() (*models.Scene, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
scenes := make([]*models.Scene, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
scenes[i], errors[i] = thunk()
}
return scenes, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Scenes.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *SceneLoader) LoadAllThunk(keys []int) func() ([]*models.Scene, []error) {
results := make([]func() (*models.Scene, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Scene, []error) {
scenes := make([]*models.Scene, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
scenes[i], errors[i] = thunk()
}
return scenes, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *SceneLoader) Prime(key int, value *models.Scene) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *SceneLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *SceneLoader) unsafeSet(key int, value *models.Scene) {
if l.cache == nil {
l.cache = map[int]*models.Scene{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *sceneLoaderBatch) keyIndex(l *SceneLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *sceneLoaderBatch) startTimer(l *SceneLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *sceneLoaderBatch) end(l *SceneLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// StudioLoaderConfig captures the config to create a new StudioLoader
type StudioLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Studio, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewStudioLoader creates a new StudioLoader given a fetch, wait, and maxBatch
func NewStudioLoader(config StudioLoaderConfig) *StudioLoader {
return &StudioLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// StudioLoader batches and caches requests
type StudioLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Studio, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Studio
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *studioLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type studioLoaderBatch struct {
keys []int
data []*models.Studio
error []error
closing bool
done chan struct{}
}
// Load a Studio by key, batching and caching will be applied automatically
func (l *StudioLoader) Load(key int) (*models.Studio, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Studio.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *StudioLoader) LoadThunk(key int) func() (*models.Studio, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Studio, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &studioLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Studio, error) {
<-batch.done
var data *models.Studio
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *StudioLoader) LoadAll(keys []int) ([]*models.Studio, []error) {
results := make([]func() (*models.Studio, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
studios := make([]*models.Studio, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
studios[i], errors[i] = thunk()
}
return studios, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Studios.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *StudioLoader) LoadAllThunk(keys []int) func() ([]*models.Studio, []error) {
results := make([]func() (*models.Studio, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Studio, []error) {
studios := make([]*models.Studio, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
studios[i], errors[i] = thunk()
}
return studios, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *StudioLoader) Prime(key int, value *models.Studio) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *StudioLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *StudioLoader) unsafeSet(key int, value *models.Studio) {
if l.cache == nil {
l.cache = map[int]*models.Studio{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *studioLoaderBatch) keyIndex(l *StudioLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *studioLoaderBatch) startTimer(l *StudioLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *studioLoaderBatch) end(l *StudioLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,224 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
"github.com/stashapp/stash/pkg/models"
)
// TagLoaderConfig captures the config to create a new TagLoader
type TagLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Tag, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
// MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
MaxBatch int
}
// NewTagLoader creates a new TagLoader given a fetch, wait, and maxBatch
func NewTagLoader(config TagLoaderConfig) *TagLoader {
return &TagLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// TagLoader batches and caches requests
type TagLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Tag, []error)
// how long to done before sending a batch
wait time.Duration
// this will limit the maximum number of keys to send in one batch, 0 = no limit
maxBatch int
// INTERNAL
// lazily created cache
cache map[int]*models.Tag
// the current batch. keys will continue to be collected until timeout is hit,
// then everything will be sent to the fetch method and out to the listeners
batch *tagLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type tagLoaderBatch struct {
keys []int
data []*models.Tag
error []error
closing bool
done chan struct{}
}
// Load a Tag by key, batching and caching will be applied automatically
func (l *TagLoader) Load(key int) (*models.Tag, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Tag.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *TagLoader) LoadThunk(key int) func() (*models.Tag, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Tag, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &tagLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Tag, error) {
<-batch.done
var data *models.Tag
if pos < len(batch.data) {
data = batch.data[pos]
}
var err error
// its convenient to be able to return a single error for everything
if len(batch.error) == 1 {
err = batch.error[0]
} else if batch.error != nil {
err = batch.error[pos]
}
if err == nil {
l.mu.Lock()
l.unsafeSet(key, data)
l.mu.Unlock()
}
return data, err
}
}
// LoadAll fetches many keys at once. It will be broken into appropriate sized
// sub batches depending on how the loader is configured
func (l *TagLoader) LoadAll(keys []int) ([]*models.Tag, []error) {
results := make([]func() (*models.Tag, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
tags := make([]*models.Tag, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
tags[i], errors[i] = thunk()
}
return tags, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Tags.
// This method should be used if you want one goroutine to make requests to many
// different data loaders without blocking until the thunk is called.
func (l *TagLoader) LoadAllThunk(keys []int) func() ([]*models.Tag, []error) {
results := make([]func() (*models.Tag, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Tag, []error) {
tags := make([]*models.Tag, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
tags[i], errors[i] = thunk()
}
return tags, errors
}
}
// Prime the cache with the provided key and value. If the key already exists, no change is made
// and false is returned.
// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
func (l *TagLoader) Prime(key int, value *models.Tag) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
// make a copy when writing to the cache, its easy to pass a pointer in from a loop var
// and end up with the whole cache pointing to the same value.
cpy := *value
l.unsafeSet(key, &cpy)
}
l.mu.Unlock()
return !found
}
// Clear the value at key from the cache, if it exists
func (l *TagLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *TagLoader) unsafeSet(key int, value *models.Tag) {
if l.cache == nil {
l.cache = map[int]*models.Tag{}
}
l.cache[key] = value
}
// keyIndex will return the location of the key in the batch, if its not found
// it will add the key to the batch
func (b *tagLoaderBatch) keyIndex(l *TagLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
}
}
pos := len(b.keys)
b.keys = append(b.keys, key)
if pos == 0 {
go b.startTimer(l)
}
if l.maxBatch != 0 && pos >= l.maxBatch-1 {
if !b.closing {
b.closing = true
l.batch = nil
go b.end(l)
}
}
return pos
}
func (b *tagLoaderBatch) startTimer(l *TagLoader) {
time.Sleep(l.wait)
l.mu.Lock()
// we must have hit a batch limit and are already finalizing this batch
if b.closing {
l.mu.Unlock()
return
}
l.batch = nil
l.mu.Unlock()
b.end(l)
}
func (b *tagLoaderBatch) end(l *TagLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

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

@@ -0,0 +1,50 @@
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"),
language.MustParse("cs-CZ"),
language.MustParse("bn-BD"),
language.MustParse("et-EE"),
language.MustParse("fa-IR"),
language.MustParse("hu-HU"),
language.MustParse("ro-RO"),
language.MustParse("th-TH"),
language.MustParse("uk-UA"),
})
// 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...)
}

261
internal/api/resolver.go Normal file
View File

@@ -0,0 +1,261 @@
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"
"github.com/stashapp/stash/pkg/txn"
)
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 {
ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string)
}
type Resolver struct {
txnManager txn.Manager
repository manager.Repository
sceneService manager.SceneService
imageService manager.ImageService
galleryService manager.GalleryService
hookExecutor hookExecutor
}
func (r *Resolver) scraperCache() *scraper.Cache {
return manager.GetInstance().ScraperCache
}
func (r *Resolver) Gallery() GalleryResolver {
return &galleryResolver{r}
}
func (r *Resolver) Mutation() MutationResolver {
return &mutationResolver{r}
}
func (r *Resolver) Performer() PerformerResolver {
return &performerResolver{r}
}
func (r *Resolver) Query() QueryResolver {
return &queryResolver{r}
}
func (r *Resolver) Scene() SceneResolver {
return &sceneResolver{r}
}
func (r *Resolver) Image() ImageResolver {
return &imageResolver{r}
}
func (r *Resolver) SceneMarker() SceneMarkerResolver {
return &sceneMarkerResolver{r}
}
func (r *Resolver) Studio() StudioResolver {
return &studioResolver{r}
}
func (r *Resolver) Movie() MovieResolver {
return &movieResolver{r}
}
func (r *Resolver) Subscription() SubscriptionResolver {
return &subscriptionResolver{r}
}
func (r *Resolver) Tag() TagResolver {
return &tagResolver{r}
}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type subscriptionResolver struct{ *Resolver }
type galleryResolver struct{ *Resolver }
type performerResolver struct{ *Resolver }
type sceneResolver struct{ *Resolver }
type sceneMarkerResolver struct{ *Resolver }
type imageResolver struct{ *Resolver }
type studioResolver struct{ *Resolver }
type movieResolver struct{ *Resolver }
type tagResolver struct{ *Resolver }
func (r *Resolver) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithTxn(ctx, r.txnManager, fn)
}
func (r *Resolver) withReadTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithReadTxn(ctx, r.txnManager, fn)
}
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.SceneMarker.Wall(ctx, q)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *queryResolver) SceneWall(ctx context.Context, q *string) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Scene.Wall(ctx, q)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *string) (ret []*models.MarkerStringsResultType, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.SceneMarker.GetMarkerStrings(ctx, q, sort)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
var ret StatsResultType
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
repo := r.repository
scenesQB := repo.Scene
imageQB := repo.Image
galleryQB := repo.Gallery
studiosQB := repo.Studio
performersQB := repo.Performer
moviesQB := repo.Movie
tagsQB := repo.Tag
scenesCount, _ := scenesQB.Count(ctx)
scenesSize, _ := scenesQB.Size(ctx)
scenesDuration, _ := scenesQB.Duration(ctx)
imageCount, _ := imageQB.Count(ctx)
imageSize, _ := imageQB.Size(ctx)
galleryCount, _ := galleryQB.Count(ctx)
performersCount, _ := performersQB.Count(ctx)
studiosCount, _ := studiosQB.Count(ctx)
moviesCount, _ := moviesQB.Count(ctx)
tagsCount, _ := tagsQB.Count(ctx)
ret = StatsResultType{
SceneCount: scenesCount,
ScenesSize: scenesSize,
ScenesDuration: scenesDuration,
ImageCount: imageCount,
ImagesSize: imageSize,
GalleryCount: galleryCount,
PerformerCount: performersCount,
StudioCount: studiosCount,
MovieCount: moviesCount,
TagCount: tagsCount,
}
return nil
}); err != nil {
return nil, err
}
return &ret, nil
}
func (r *queryResolver) Version(ctx context.Context) (*Version, error) {
version, hash, buildtime := GetVersion()
return &Version{
Version: &version,
Hash: hash,
BuildTime: buildtime,
}, nil
}
// Latestversion returns the latest git shorthash commit.
func (r *queryResolver) Latestversion(ctx context.Context) (*ShortVersion, error) {
ver, url, err := GetLatestVersion(ctx, true)
if err == nil {
logger.Infof("Retrieved latest hash: %s", ver)
} else {
logger.Errorf("Error while retrieving latest hash: %s", err)
}
return &ShortVersion{
Shorthash: ver,
URL: url,
}, err
}
// Get scene marker tags which show up under the video.
func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*SceneMarkerTag, error) {
sceneID, err := strconv.Atoi(scene_id)
if err != nil {
return nil, err
}
var keys []int
tags := make(map[int]*SceneMarkerTag)
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
sceneMarkers, err := r.repository.SceneMarker.FindBySceneID(ctx, sceneID)
if err != nil {
return err
}
tqb := r.repository.Tag
for _, sceneMarker := range sceneMarkers {
markerPrimaryTag, err := tqb.Find(ctx, sceneMarker.PrimaryTagID)
if err != nil {
return err
}
_, hasKey := tags[markerPrimaryTag.ID]
if !hasKey {
sceneMarkerTag := &SceneMarkerTag{Tag: markerPrimaryTag}
tags[markerPrimaryTag.ID] = sceneMarkerTag
keys = append(keys, markerPrimaryTag.ID)
}
tags[markerPrimaryTag.ID].SceneMarkers = append(tags[markerPrimaryTag.ID].SceneMarkers, sceneMarker)
}
return nil
}); err != nil {
return nil, err
}
// Sort so that primary tags that show up earlier in the video are first.
sort.Slice(keys, func(i, j int) bool {
a := tags[keys[i]]
b := tags[keys[j]]
return a.SceneMarkers[0].Seconds < b.SceneMarkers[0].Seconds
})
var result []*SceneMarkerTag
for _, key := range keys {
result = append(result, tags[key])
}
return result, nil
}
func firstError(errs []error) error {
for _, e := range errs {
if e != nil {
return e
}
}
return nil
}

View File

@@ -0,0 +1,250 @@
package api
import (
"context"
"strconv"
"time"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
func (r *galleryResolver) getPrimaryFile(ctx context.Context, obj *models.Gallery) (file.File, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
return nil, err
}
return f, nil
}
return nil, nil
}
func (r *galleryResolver) getFiles(ctx context.Context, obj *models.Gallery) ([]file.File, error) {
fileIDs, err := loaders.From(ctx).GalleryFiles.Load(obj.ID)
if err != nil {
return nil, err
}
files, errs := loaders.From(ctx).FileByID.LoadAll(fileIDs)
return files, firstError(errs)
}
func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]*GalleryFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
ret := make([]*GalleryFile, len(files))
for i, f := range files {
base := f.Base()
ret[i] = &GalleryFile{
ID: strconv.Itoa(int(base.ID)),
Path: base.Path,
Basename: base.Basename,
ParentFolderID: strconv.Itoa(int(base.ParentFolderID)),
ModTime: base.ModTime,
Size: base.Size,
CreatedAt: base.CreatedAt,
UpdatedAt: base.UpdatedAt,
Fingerprints: resolveFingerprints(base),
}
if base.ZipFileID != nil {
zipFileID := strconv.Itoa(int(*base.ZipFileID))
ret[i].ZipFileID = &zipFileID
}
}
return ret, nil
}
func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*Folder, error) {
if obj.FolderID == nil {
return nil, nil
}
var ret *file.Folder
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = r.repository.Folder.Find(ctx, *obj.FolderID)
if err != nil {
return err
}
return err
}); err != nil {
return nil, err
}
if ret == nil {
return nil, nil
}
rr := &Folder{
ID: ret.ID.String(),
Path: ret.Path,
ModTime: ret.ModTime,
CreatedAt: ret.CreatedAt,
UpdatedAt: ret.UpdatedAt,
}
if ret.ParentFolderID != nil {
pfidStr := ret.ParentFolderID.String()
rr.ParentFolderID = &pfidStr
}
if ret.ZipFileID != nil {
zfidStr := ret.ZipFileID.String()
rr.ZipFileID = &zfidStr
}
return rr, nil
}
func (r *galleryResolver) FileModTime(ctx context.Context, obj *models.Gallery) (*time.Time, error) {
f, err := r.getPrimaryFile(ctx, obj)
if err != nil {
return nil, err
}
if f != nil {
return &f.Base().ModTime, nil
}
return nil, nil
}
// Images is deprecated, slow and shouldn't be used
func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) (ret []*models.Image, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
// #2376 - sort images by path
// doing this via Query is really slow, so stick with FindByGalleryID
ret, err = r.repository.Image.FindByGalleryID(ctx, obj.ID)
if err != nil {
return err
}
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (ret *models.Image, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
// find cover.jpg first
ret, err = image.FindGalleryCover(ctx, r.repository.Image, obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *galleryResolver) Date(ctx context.Context, obj *models.Gallery) (*string, error) {
if obj.Date != nil {
result := obj.Date.String()
return &result, nil
}
return nil, nil
}
func (r *galleryResolver) Checksum(ctx context.Context, obj *models.Gallery) (string, error) {
if !obj.Files.PrimaryLoaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadPrimaryFile(ctx, r.repository.File)
}); err != nil {
return "", err
}
}
return obj.PrimaryChecksum(), nil
}
func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int, error) {
if obj.Rating != nil {
rating := models.Rating100To5(*obj.Rating)
return &rating, nil
}
return nil, nil
}
func (r *galleryResolver) Rating100(ctx context.Context, obj *models.Gallery) (*int, error) {
return obj.Rating, nil
}
func (r *galleryResolver) Scenes(ctx context.Context, obj *models.Gallery) (ret []*models.Scene, err error) {
if !obj.SceneIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadSceneIDs(ctx, r.repository.Gallery)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).SceneByID.LoadAll(obj.SceneIDs.List())
return ret, firstError(errs)
}
func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (ret *models.Studio, err error) {
if obj.StudioID == nil {
return nil, nil
}
return loaders.From(ctx).StudioByID.Load(*obj.StudioID)
}
func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) (ret []*models.Tag, err error) {
if !obj.TagIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadTagIDs(ctx, r.repository.Gallery)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
return ret, firstError(errs)
}
func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) (ret []*models.Performer, err error) {
if !obj.PerformerIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadPerformerIDs(ctx, r.repository.Gallery)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).PerformerByID.LoadAll(obj.PerformerIDs.List())
return ret, firstError(errs)
}
func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = r.repository.Image.CountByGalleryID(ctx, obj.ID)
return err
}); err != nil {
return 0, err
}
return ret, nil
}

View File

@@ -0,0 +1,193 @@
package api
import (
"context"
"fmt"
"strconv"
"time"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/file"
"github.com/stashapp/stash/pkg/models"
)
func (r *imageResolver) getPrimaryFile(ctx context.Context, obj *models.Image) (*file.ImageFile, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
return nil, err
}
ret, ok := f.(*file.ImageFile)
if !ok {
return nil, fmt.Errorf("file %T is not an image file", f)
}
return ret, nil
}
return nil, nil
}
func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]*file.ImageFile, error) {
fileIDs, err := loaders.From(ctx).ImageFiles.Load(obj.ID)
if err != nil {
return nil, err
}
files, errs := loaders.From(ctx).FileByID.LoadAll(fileIDs)
ret := make([]*file.ImageFile, len(files))
for i, bf := range files {
f, ok := bf.(*file.ImageFile)
if !ok {
return nil, fmt.Errorf("file %T is not an image file", f)
}
ret[i] = f
}
return ret, firstError(errs)
}
func (r *imageResolver) Title(ctx context.Context, obj *models.Image) (*string, error) {
ret := obj.GetTitle()
return &ret, nil
}
func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*ImageFileType, error) {
f, err := r.getPrimaryFile(ctx, obj)
if err != nil {
return nil, err
}
if f == nil {
return nil, nil
}
width := f.Width
height := f.Height
size := f.Size
return &ImageFileType{
Size: int(size),
Width: width,
Height: height,
}, nil
}
func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*ImageFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
ret := make([]*ImageFile, len(files))
for i, f := range files {
ret[i] = &ImageFile{
ID: strconv.Itoa(int(f.ID)),
Path: f.Path,
Basename: f.Basename,
ParentFolderID: strconv.Itoa(int(f.ParentFolderID)),
ModTime: f.ModTime,
Size: f.Size,
Width: f.Width,
Height: f.Height,
CreatedAt: f.CreatedAt,
UpdatedAt: f.UpdatedAt,
Fingerprints: resolveFingerprints(f.Base()),
}
if f.ZipFileID != nil {
zipFileID := strconv.Itoa(int(*f.ZipFileID))
ret[i].ZipFileID = &zipFileID
}
}
return ret, nil
}
func (r *imageResolver) FileModTime(ctx context.Context, obj *models.Image) (*time.Time, error) {
f, err := r.getPrimaryFile(ctx, obj)
if err != nil {
return nil, err
}
if f != nil {
return &f.ModTime, nil
}
return nil, nil
}
func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*ImagePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewImageURLBuilder(baseURL, obj)
thumbnailPath := builder.GetThumbnailURL()
imagePath := builder.GetImageURL()
return &ImagePathsType{
Image: &imagePath,
Thumbnail: &thumbnailPath,
}, nil
}
func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret []*models.Gallery, err error) {
if !obj.GalleryIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadGalleryIDs(ctx, r.repository.Image)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).GalleryByID.LoadAll(obj.GalleryIDs.List())
return ret, firstError(errs)
}
func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, error) {
if obj.Rating != nil {
rating := models.Rating100To5(*obj.Rating)
return &rating, nil
}
return nil, nil
}
func (r *imageResolver) Rating100(ctx context.Context, obj *models.Image) (*int, error) {
return obj.Rating, nil
}
func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (ret *models.Studio, err error) {
if obj.StudioID == nil {
return nil, nil
}
return loaders.From(ctx).StudioByID.Load(*obj.StudioID)
}
func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) (ret []*models.Tag, err error) {
if !obj.TagIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadTagIDs(ctx, r.repository.Image)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
return ret, firstError(errs)
}
func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) (ret []*models.Performer, err error) {
if !obj.PerformerIDs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadPerformerIDs(ctx, r.repository.Image)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).PerformerByID.LoadAll(obj.PerformerIDs.List())
return ret, firstError(errs)
}

View File

@@ -4,7 +4,8 @@ import (
"context"
"time"
"github.com/stashapp/stash/pkg/api/urlbuilders"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
)
@@ -47,6 +48,14 @@ func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, e
}
func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, error) {
if obj.Rating.Valid {
rating := models.Rating100To5(int(obj.Rating.Int64))
return &rating, nil
}
return nil, nil
}
func (r *movieResolver) Rating100(ctx context.Context, obj *models.Movie) (*int, error) {
if obj.Rating.Valid {
rating := int(obj.Rating.Int64)
return &rating, nil
@@ -56,14 +65,7 @@ func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, er
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *models.Studio, err error) {
if obj.StudioID.Valid {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
ret, err = repo.Studio().Find(int(obj.StudioID.Int64))
return err
}); err != nil {
return nil, err
}
return ret, nil
return loaders.From(ctx).StudioByID.Load(int(obj.StudioID.Int64))
}
return nil, nil
@@ -92,9 +94,9 @@ func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
// don't return any thing if there is no back image
var img []byte
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
img, err = repo.Movie().GetBackImage(obj.ID)
img, err = r.repository.Movie.GetBackImage(ctx, obj.ID)
if err != nil {
return err
}
@@ -115,8 +117,8 @@ func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
res, err = repo.Scene().CountByMovieID(obj.ID)
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
res, err = r.repository.Scene.CountByMovieID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
@@ -126,9 +128,9 @@ func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret
}
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = repo.Scene().FindByMovieID(obj.ID)
ret, err = r.repository.Scene.FindByMovieID(ctx, obj.ID)
return err
}); err != nil {
return nil, err

View File

@@ -0,0 +1,151 @@
package api
import (
"context"
"strconv"
"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"
)
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Height != nil {
ret := strconv.Itoa(*obj.Height)
return &ret, nil
}
return nil, nil
}
func (r *performerResolver) HeightCm(ctx context.Context, obj *models.Performer) (*int, error) {
return obj.Height, nil
}
func (r *performerResolver) Birthdate(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Birthdate != nil {
ret := obj.Birthdate.String()
return &ret, nil
}
return nil, nil
}
func (r *performerResolver) ImagePath(ctx context.Context, obj *models.Performer) (*string, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewPerformerURLBuilder(baseURL, obj).GetPerformerImageURL()
return &imagePath, nil
}
func (r *performerResolver) Tags(ctx context.Context, obj *models.Performer) (ret []*models.Tag, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Tag.FindByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *performerResolver) SceneCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
res, err = r.repository.Scene.CountByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, nil
}
func (r *performerResolver) ImageCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
res, err = image.CountByPerformerID(ctx, r.repository.Image, obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, nil
}
func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
res, err = gallery.CountByPerformerID(ctx, r.repository.Gallery, obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, nil
}
func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Scene.FindByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) ([]*models.StashID, error) {
var ret []models.StashID
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = r.repository.Performer.GetStashIDs(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return stashIDsSliceToPtrSlice(ret), nil
}
func (r *performerResolver) Rating(ctx context.Context, obj *models.Performer) (*int, error) {
if obj.Rating != nil {
rating := models.Rating100To5(*obj.Rating)
return &rating, nil
}
return nil, nil
}
func (r *performerResolver) Rating100(ctx context.Context, obj *models.Performer) (*int, error) {
return obj.Rating, nil
}
func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.DeathDate != nil {
ret := obj.DeathDate.String()
return &ret, nil
}
return nil, nil
}
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return ret, nil
}
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret *int, err error) {
var res int
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
res, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return &res, nil
}

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