Compare commits

...

852 Commits

Author SHA1 Message Date
WithoutPants
35c5fdbe91 Revert "Feature Request: Sort All Urls Alphabetically (#6352)"
This reverts commit 061d21dede.
2025-12-11 11:17:55 +11:00
ghuds540
a4816b4cc9 Respect user preference for type-to-create in image/scene multi-select form (#6376) 2025-12-11 08:22:29 +11:00
Gykes
ba0102f2a6 use initialstate for scene performers in tagger (#6391) 2025-12-11 08:07:16 +11:00
CJ
fe41561dfe add autostart button to videoplayer (#6368) 2025-12-11 08:01:38 +11:00
WithoutPants
7fded66bfa Improve tag stash-id handling in tagger and scraper dialogs (#6389)
* Change link button icon and separate into component
* Add create/link tag dialog
* Add titles to buttons
* Add ability to link existing tags in scrape dialogs
* Move create link dialog
* Allow tags to have multiple stash-ids from the same endpoint
2025-12-09 13:55:11 +11:00
WithoutPants
945d679158 Refactor and restyle scrape dialog on smaller viewports (#6387)
* Improve string-list-input styling
* Rename ScrapedDialog file
* Move ScrapeDialog into separate file
* Refactor scrape dialog row inputs
* Refactor new value handling
* Add context for labels
* Refactor scrape dialog to accept children
* Add existing/scraped labels for smaller viewports
2025-12-09 07:29:41 +11:00
WithoutPants
7db394bbea Date precision (#6359)
* Remove month/year only formats from ParseDateStringAsTime
* Add precision field to Date and handle parsing year/month-only dates
* Add date precision columns for date columns
* Adjust UI to account for fuzzy dates
2025-12-08 09:11:40 +11:00
WithoutPants
eb9d0705bc Query for image query result metadata separately (#6370)
* Make a separate query for loading image query metadata
2025-12-08 08:47:35 +11:00
WithoutPants
0fd7a2ac20 SQL performance improvements (#6378)
* Change queryStruct to use tx.Get instead of queryFunc

Using queryFunc meant that the performance logging was inaccurate due to the query actually being executed during the call to Scan.

* Only add join args if join was added

* Omit joins that are only used for sorting when skipping sorting

Should provide some marginal improvement on systems with a lot of items.

* Make all calls to the database pass context.

This means that long queries can be cancelled by navigating to another page. Previously the query would continue to run, impacting on future queries.
2025-12-08 08:08:31 +11:00
WithoutPants
e2dff05081 Replace ValueOnlyContext with context.WithoutCancel (#6379) 2025-12-08 07:59:42 +11:00
Gykes
061d21dede Feature Request: Sort All Urls Alphabetically (#6352) 2025-12-05 14:05:46 +11:00
WithoutPants
88a149c085 Correct sidebar styling on details pages (#6377)
* Remove margin-bottom on xs to fix styling weirdness
* Only set sidebar height when sidebar visible
2025-12-05 09:04:16 +11:00
WithoutPants
d994df2900 Don't convert config file location to absolute during setup (#6373)
This was originally done for #3304. The ffmpeg code has been redone since and this is no longer necessary. It was also resulting in the scraper and plugin paths being absolute, despite all the others being relative to the provided config path.
2025-12-05 08:46:31 +11:00
Gykes
39fd8a6550 Feature: Manual StashId Search - Tags (#6374) 2025-12-04 11:20:29 +11:00
Gykes
877491e62b Manually Search Stash ID - Edit Page - Scenes, Studios (#6340) 2025-12-04 09:09:49 +11:00
DogmaDragon
3d044896ad Update Auto Tag/Identify documentation (#6371)
* Update Auto Tag documentation
* Update Identify documentation
2025-12-04 07:48:36 +11:00
WithoutPants
63e8830db4 Truncate custom field display to 5 lines (#6361) 2025-12-04 07:28:30 +11:00
WithoutPants
0bc4faef2a Add support for removing custom field keys (#6362) 2025-12-04 07:28:06 +11:00
WithoutPants
ee61fc879b Add nil check for scraped measurements (#6367) 2025-12-04 07:27:47 +11:00
WithoutPants
e02ef436a5 Fix batch tag update when studio/performer has no stash id (#6369)
* Handle batch tagging where stash id not set

Should search by name for these

* Don't set empty stash ids
2025-12-04 07:26:41 +11:00
Shadesbird
41f0612025 Update Identify.md - Add advanced settings hint (#6372)
Did not find this feature by myself. Had to have a forum discussion to realise this feature exists and is hidden in the advanced settings.

Added hint that this is an advanced setting.
2025-12-04 07:26:23 +11:00
WithoutPants
730e877e73 [RFC] Refactor scene list toolbar (#6322)
* Revert scene list toolbar to use common filtered list toolbar
* Add unobtrusive sidebar toggle button
* Revert small device sidebar changes
* Minor styling fixes
2025-12-03 14:59:15 +11:00
WithoutPants
e213fde0cc Return error when scanning avif in zip (#6356) 2025-12-02 14:27:29 +11:00
hckrman101
69fd073d5d Add option for instant transitions in lightbox (#6354) 2025-12-02 14:25:46 +11:00
Otter Bot Society
5f16547e58 Add fallback for 0-dimension images (webp animations) (#6342) 2025-12-02 14:14:42 +11:00
feederbox826
90dd0b58d8 add WakeLockSentinel (#6331)
* add WakeLockSentinel

prevents screen from sleeping ONLY in secure contexts (localhost, https)

closes #2884

* format, add types

* [wake-sentinel] add more releases, comments

release wakelock on dispose and end, call out secure contexts in error message
2025-12-02 12:57:54 +11:00
WithoutPants
4017c42fe2 Handle modified files where the case of the filename changed on case-insensitive filesystems (#6327)
* Find existing files with case insensitivity if filesystem is case insensitive
* Handle case change in folders
* Optimise to only test file system case sensitivity if the first query found nothing

This limits the overhead to new paths, and adds an extra query for new paths to windows installs
2025-12-02 12:53:37 +11:00
Gykes
49fd47562e Bugfix: Fix New Tagger Gender Setting Select (#6351) 2025-12-02 12:52:16 +11:00
WithoutPants
84e24eb612 Refactor scraping to include related object fields (#6266)
* Refactor scraper post-processing and process related objects consistently
* Refactor image processing
* Scrape related studio fields consistently
* Don't set image on related objects
2025-12-02 12:49:44 +11:00
Gykes
c6ae43c1d6 Feature Request: Vips AVIF Support (#6337) 2025-11-28 15:00:23 +11:00
dependabot[bot]
de8139cf1b Bump golang.org/x/crypto from 0.38.0 to 0.45.0 (#6300)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.38.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.38.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-28 14:05:19 +11:00
WithoutPants
0ca416f75a Ignore empty alias in studio partial (#6338) 2025-11-28 13:54:21 +11:00
WithoutPants
1bc32a3099 Add sticky selection toolbar (#6320) 2025-11-28 13:52:30 +11:00
WithoutPants
d1ee64d36f Change show male performers option into list of gender checkboxes (#6321) 2025-11-28 13:51:20 +11:00
Gykes
e052a431d1 Feature Request: Bulk Add by StashID and Name (#6310) 2025-11-28 13:19:14 +11:00
feederbox826
7e66ce8a49 trigger play count on player ended (#6334) 2025-11-28 11:56:54 +11:00
Gykes
88747b962a allow partial dates (#6333) 2025-11-28 11:55:18 +11:00
Gykes
97c01c70b3 update mac notification (#6329) 2025-11-28 11:48:23 +11:00
feederbox826
a3ed381901 [hwaccel] increase timeout, fix formatting (#6328)
Thanks to @Gykes for helping find the formatting error
2025-11-28 11:47:34 +11:00
DogmaDragon
b3da730a05 docs: Improve README clarity and formatting 2025-11-28 00:24:06 +02:00
Gykes
e0c1d4c51d Manually Search Stash ID - Edit Page (#6284) 2025-11-28 07:32:29 +11:00
Gykes
90d1b2df2d Feature: AVIF support (#6288) 2025-11-28 07:19:32 +11:00
Gykes
4ef3a605dd Bugfix: Update Markers to % Base Calc (#6323)
* update to % base calc
* add min-width
2025-11-27 14:57:57 +11:00
WithoutPants
f811590021 Debug log stderr when thumbnail generation fails 2025-11-27 14:04:06 +11:00
Gykes
0bd78f4b62 Bugfix: Add Trimspace to New Objects (#6226) 2025-11-27 07:48:56 +11:00
Gykes
a8bb9ae4d3 Show fingerprints when 0 scens (#6316) 2025-11-26 13:57:15 +11:00
Gykes
d10995302d Feature: Add trash support (#6237) 2025-11-26 13:38:19 +11:00
Gykes
d14053b570 Bugfix: Tagger Ignoing Disambiguation When Linking Performer (#6308) 2025-11-26 12:06:13 +11:00
WithoutPants
ca357b9eb3 Codeberg weblate (#6318)
* Translated using Weblate (Russian)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ru/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (German)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (French)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Japanese)

Currently translated at 83.9% (1026 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Spanish)

Currently translated at 96.7% (1182 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (French)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1222 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Ukrainian)

Currently translated at 87.4% (1069 of 1222 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/

* Translated using Weblate (Estonian)

Currently translated at 98.7% (1217 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (French)

Currently translated at 100.0% (1233 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Czech)

Currently translated at 98.4% (1214 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.6% (1229 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1233 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 99.0% (1221 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Spanish)

Currently translated at 96.8% (1194 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Japanese)

Currently translated at 82.7% (1020 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (German)

Currently translated at 99.3% (1225 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Spanish)

Currently translated at 97.2% (1199 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Dutch)

Currently translated at 79.1% (976 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Bulgarian)

Currently translated at 25.1% (310 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/bg/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1233 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1233 of 1233 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

---------

Co-authored-by: direnyx <direnyx@noreply.codeberg.org>
Co-authored-by: lugged9922 <lugged9922@noreply.codeberg.org>
Co-authored-by: yec <yec@noreply.codeberg.org>
Co-authored-by: Marly21 <marly21@noreply.codeberg.org>
Co-authored-by: doodoo <doodoo@noreply.codeberg.org>
Co-authored-by: tobakumap <tobakumap@noreply.codeberg.org>
Co-authored-by: Zesty6249 <zesty6249@noreply.codeberg.org>
Co-authored-by: wql219 <wql219@noreply.codeberg.org>
Co-authored-by: donlothario <donlothario@noreply.codeberg.org>
Co-authored-by: danny60718 <danny60718@noreply.codeberg.org>
Co-authored-by: AlpacaSerious <alpacaserious@noreply.codeberg.org>
Co-authored-by: ves10023 <ves10023@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: NymeriaCZ <nymeriacz@noreply.codeberg.org>
Co-authored-by: 2307777 <2307777@noreply.codeberg.org>
Co-authored-by: hirokazuk <hirokazuk@noreply.codeberg.org>
Co-authored-by: PhilipWaldman <philipwaldman@noreply.codeberg.org>
Co-authored-by: Gundir <gundir@noreply.codeberg.org>
2025-11-25 17:46:23 +11:00
WithoutPants
6892c7151c Update changelog 2025-11-25 17:37:52 +11:00
WithoutPants
d6a2953371 Refactor filtered list toolbar (#6317)
* Refactor list operation buttons into a single button group
* Refactor ListFilter into FilteredListToolbar and restyle
* Move zoom keybinds out of zoom control
* Use button group for display mode select
* Hide zoom slider on xs devices
2025-11-25 17:36:13 +11:00
feederbox826
50ad3c0778 [MediaSession] fall back to performers if studio not available (#6315) 2025-11-25 14:41:01 +11:00
WithoutPants
dc520e2b2f Ignore empty studio alias in ScrapedStudio (#6313) 2025-11-25 10:11:39 +11:00
Slick Daddy
ecd9c6ec5b Show O Counter in Studio card (#5982)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-25 10:06:36 +11:00
feederbox826
ca8ee6bc2a add MediaSession plugin (#6298) 2025-11-25 09:12:23 +11:00
Gykes
5d02f916c2 Check for dupe IDs against boxes (#6309) 2025-11-25 08:58:57 +11:00
DogmaDragon
e176cf5f71 Document "# requires" in the plugin config (#6306)
* Document "# requires" in the plugin config
* Add missing line breaks in UIPluginApi documentation
2025-11-25 08:35:05 +11:00
Gykes
2cac7d5b20 Bugfix: Add extra date formats. (#6305) 2025-11-25 08:17:51 +11:00
feederbox826
58b6833380 make airplay follow chromecast enable (#6296) 2025-11-19 13:29:15 +11:00
feederbox826
68ebeda5c8 Sanitise intent URL (#6297) 2025-11-19 13:28:20 +11:00
NodudeWasTaken
2332401dbf Fix missing saved filter overwrite translation (#6294)
This translation was renamed from _confirm to _warning.
2025-11-19 09:10:00 +11:00
feederbox826
33b59e02af [markers] ignore generating markers past end (#6290) 2025-11-18 15:07:08 +11:00
Gykes
367b96df0f Bug Fix: Update Macos Version Check (#6289) 2025-11-18 15:06:25 +11:00
Gykes
a31df336f8 Remove style for Studio URLs (#6291) 2025-11-18 15:05:55 +11:00
feederbox826
78aeb06f20 add lumberjack log rotation (#5696)
* [logging] add UI and graphql for maximum log size
* [logger] set default size to 0MB and don't rotate
2025-11-18 14:04:22 +11:00
WithoutPants
2f65a1da3e Revert form changes from #6262
Removes the components inside the formikUtils function, which was causing incorrect re-renders.

Adds data-field to renderField instead, which is a far more simple change.
2025-11-18 13:45:37 +11:00
WithoutPants
51999135be Add SFW content mode option (#6262)
* Use more neutral language for content
* Add sfw mode setting
* Make configuration context mandatory
* Add sfw class when sfw mode active
* Hide nsfw performer fields in sfw mode
* Hide nsfw sort options
* Hide nsfw filter/sort options in sfw mode
* Replace o-count with like counter in sfw mode
* Use sfw label for o-counter filter in sfw mode
* Use likes instead of o-count in sfw mode in other places
* Rename sfw mode to sfw content mode
* Use sfw image for default performers in sfw mode
* Document SFW content mode
* Add SFW mode setting to setup
* Clarify README
* Change wording of sfw mode description
* Handle configuration loading error correctly
* Hide age in performer cards
2025-11-18 11:13:35 +11:00
Gykes
bb56b619f5 Add Markers Filter (#6270) 2025-11-17 12:13:13 +11:00
Gykes
a590caa3d3 FR: Performer Age Slider (#6267)
- Add SidebarAgeFilter component with age presets (18-25, 25-35, 35-45, 45-60, 60+)
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-17 11:20:38 +11:00
DogmaDragon
0a05a0b45b i18n: Change 'Has Chapters' to 'Chapters' (#6279) 2025-11-17 10:29:09 +11:00
WithoutPants
9ef2169055 Add edit scene markers dialog (#6239) 2025-11-17 10:13:34 +11:00
WithoutPants
1ec8d4afe5 Add edit studios dialog (#6238) 2025-11-17 10:12:50 +11:00
WithoutPants
15db2da361 Add v0.30.0 changelog 2025-11-14 13:41:29 +11:00
WithoutPants
892858a803 Trigger build when release branch pushed 2025-11-14 13:08:12 +11:00
WithoutPants
bc91ca0a25 Fix inconsistency when scraping performer with multiple stash ids from same endpoint (#6260) 2025-11-14 12:59:29 +11:00
WithoutPants
d743787bb3 Include stash-ids when creating objects from scrape dialog (#6269)
* Filter out empty aliases
2025-11-14 12:57:34 +11:00
Gykes
957c4fe1b5 Bugfix: Fix empty Aliases Being Created for Studios (#6273)
* Filter out empty alias strings in studio modal create
* Reject empty alias strings in backend
* Remove invalid ValidateAliases call from UpdatePartial

This was calling using the values which are not necessarily the final values.
---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-14 11:49:26 +11:00
Gykes
e3b3fbbf63 FR: Add Duration Slider to Sidebar Filters (#6264)
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-14 09:12:06 +11:00
Gykes
c99825a453 Feature: Tag StashID support (#6255) 2025-11-13 14:24:09 +11:00
Gykes
a08d2e258a Feature: Add Various Scraper Fields (#6249)
* Support aliases in stashbox studio query
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-13 10:14:04 +11:00
Gykes
b2c8f09585 add tagger shortcut (#6261) 2025-11-12 16:58:30 +11:00
feederbox826
5e34df7b7b [ui] add playsInline to every image/video elem (#6259) 2025-11-12 14:09:14 +11:00
Gykes
678b3de7c8 Feature: Support inputURL and inputHostname in scrapers (#6250) 2025-11-10 15:00:47 +11:00
Gykes
f434c1f529 Feature: Support Multiple URLs in Studios (#6223)
* Backend support for studio URLs
* FrontEnd addition
* Support URLs in BulkStudioUpdate
* Update tagger modal for URLs
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-10 14:34:21 +11:00
n0ld069
12a9a0b5f6 Add keyboard shortcuts for Scene Cover generation (#5984)
* Add keyboard shortcuts for screenshot generation

- Add 'c c' shortcut to generate screenshot at current time
- Add 'c d' shortcut to generate default screenshot
- Update keyboard shortcuts documentation
2025-11-10 12:11:37 +11:00
theqwertyqwert
34becdf436 Add external links display option for performer thumbnails (#6153)
* Add external links display option for performer thumbnails

- Introduced a new setting to show links on performer thumbnails.
- Updated PerformerCard to conditionally render social media links (Twitter, Instagram) and other external links.
- Enhanced ExternalLinksButton to open single links directly if specified.
- Updated configuration and localization files to support the new feature.
2025-11-10 11:54:44 +11:00
EventHoriizon
d5b1046267 Group O-Counter Filter/Sort (#6122) 2025-11-10 11:53:53 +11:00
dependabot[bot]
2e766952dd Bump github.com/go-chi/chi/v5 from 5.0.12 to 5.2.2 (#5948)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.12 to 5.2.2.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.12...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-version: 5.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 11:43:04 +11:00
melon-scientist
1cc983fb5b Add O-Count to performer page (#6171) 2025-11-10 11:33:15 +11:00
Ian McKenzie
a76e515112 Bump vite from 4.5.14 to 5.4.21 in /ui/v2.5 (#6229)
* Bump vite from 4.5.14 to 5.4.21 in /ui/v2.5

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.14 to 5.4.21.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.21/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.21
  dependency-type: direct:development
- dependency-name: @vitejs/plugin-legacy
  dependency-version: 5.4.3
  dependency-type: direct:development
- dependency-name: @vitejs/plugin-react
  dependency-version: 5.1.0
  dependency-type: direct:development
...

* Update lock file

* Remove intersection-observer

Apparently not necessary any more. Resolves deprecation message

* Remove version from package file

---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-10 11:11:53 +11:00
BigBangClock2
1a9a62eae9 Add sorting by performer age (#6009) 2025-11-10 10:49:40 +11:00
damontecres
638ebfc319 Support markers on the front page (#6065) 2025-11-10 10:48:59 +11:00
Gykes
53655e51c4 Feature: Filter by Total Scene Duration (#6172) 2025-11-10 10:45:36 +11:00
ayaya
289b698598 Add hardware codec support for rkmpp (#6182) 2025-11-10 09:55:12 +11:00
WithoutPants
b4d148bdb0 Delete temp file before running backup (#6248) 2025-11-10 09:20:48 +11:00
feederbox826
600cb15102 [packaging] switch to pnpm (#6186)
* [packaging] switch to pnpm
* Bump compiler version
* Change pnpm store in docker build
---------

Co-authored-by: feederbox826 <feederbox826@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-11-10 09:17:25 +11:00
WithoutPants
d52b6afd4a Separate search clear effect from config saving (#6247)
Fixes Scrape results cache eviction in tagger view
2025-11-07 15:11:17 +11:00
stashcoder42
96a7e087f2 Upgrade to koanf 2.2.1 (#5985) 2025-11-06 18:20:52 +11:00
Colin Alexander Duffy
20fa5d3146 Add JXL (#6184) 2025-11-06 18:09:40 +11:00
dependabot[bot]
095e5d50ab Bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.4.0 (#6061)
Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.4.0.
- [Release notes](https://github.com/go-viper/mapstructure/releases)
- [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/go-viper/mapstructure/v2
  dependency-version: 2.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-06 17:33:35 +11:00
Gykes
42f76ca34f Filter by Studio (#6155) 2025-11-06 17:26:30 +11:00
WithoutPants
a50a0d4289 Related files/folder filter for scenes/images/galleries (#6158)
* Add related files filter to scene filter
* Add files_filter to gallery filter
* Add files_filter to image filter
* Add gallery related folder filter
2025-11-06 17:25:59 +11:00
WithoutPants
04fcf6f512 Merge branch 'releases/0.29.3' into develop 2025-11-06 17:21:12 +11:00
WithoutPants
7716c4dd87 Update changelog 2025-11-06 16:55:40 +11:00
WithoutPants
2925325e68 Fix contents not loading in filter sidebar (#6240) 2025-11-06 16:54:53 +11:00
smith113-p
d831e4573c Remember the selected stash box in scene tagger (#6192)
* Remember the selected stash box in scene tagger

This stores the selected stash box in the config, the same way that
studio and performer tagger do (it uses the same setting).

If a non-stashbox source (scraper) is selected, this is not remembered.
2025-11-06 16:20:17 +11:00
Ian McKenzie
1b864f28f6 Remove custom ffmpeg compile step from OpenBSD dev instructions (#6219)
ffmpeg has been compiled with webp support since 7.7, so all
these extra steps are no longer needed
2025-11-06 16:09:33 +11:00
WithoutPants
8c4b607454 Add bulk update markers interface (#6210) 2025-11-06 16:01:24 +11:00
WithoutPants
2a2a730296 Add interface for bulk update studio (#6208) 2025-11-06 15:59:55 +11:00
WithoutPants
beee37bc38 Codeberg weblate (#6235)
* Translated using Weblate (Bulgarian)

Currently translated at 25.0% (305 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/bg/

* Translated using Weblate (Dutch)

Currently translated at 77.1% (940 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Turkish)

Currently translated at 95.9% (1170 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

---------

Co-authored-by: theqwertyqwert <theqwertyqwert@noreply.codeberg.org>
Co-authored-by: callmenoodles <callmenoodles@noreply.codeberg.org>
Co-authored-by: slickdaddy <slickdaddy@noreply.codeberg.org>
2025-11-06 11:07:15 +11:00
WithoutPants
9be0cc3210 Update changelog 2025-11-06 10:46:37 +11:00
WithoutPants
f2a787a2ba Add (hidden) pagination to list results header (#6234) 2025-11-06 10:45:57 +11:00
Gykes
6cace4ff88 Update parser to accept groups (#6228) 2025-11-06 09:53:43 +11:00
DogmaDragon
fa2fd31ac7 Update library section in Configuration.md for clarity (#6232) 2025-11-06 08:24:33 +11:00
WithoutPants
1b2b4c5221 Fix panic when scraping with unknown field (#6220)
* Fix URL in group scraper causing panic
* Return error instead of panicking on unknown field
2025-10-31 19:54:35 +11:00
WithoutPants
336fa3b70e Save sidebar state (#6217)
* Save sidebar section open state in browser history state

This means that state is saved when going back, but not when navigating to the scenes page from elsewhere.
2025-10-31 15:21:43 +11:00
WithoutPants
299e1ac1f9 Scene list toolbar style update (#6215)
* Add saved filter button to toolbar
* Rearrange and add portal target
* Only overlap sidebar on sm viewports
* Hide dropdown button on smaller viewports when sidebar open
* Center operations during selection
* Restyle results header
* Add classname for sidebar pane content
* Move sidebar toggle to left during scene selection
2025-10-31 14:29:01 +11:00
WithoutPants
fb7bd89834 Fix update loop in Group Sub Groups panel (#6212)
* Fix location equality testing causing update loop
* Move defaultFilter out of component
* Fix add sub groups dialog dropdown render issue
2025-10-29 11:33:20 +11:00
WithoutPants
f04be76224 Don't trim query string from decoded URL params (#6211) 2025-10-29 11:13:46 +11:00
WithoutPants
db79cf9bb1 Increase number of pages in pagination dropdown to 1000 (#6207) 2025-10-29 11:13:29 +11:00
WithoutPants
90baa31ee3 Hide zoom slider in xs viewports (#6206)
The zoom slider doesn't function in this viewport so it shouldn't be shown.
2025-10-29 11:13:13 +11:00
WithoutPants
9b8300e882 Only scroll edit filter dialog when clicking filter tag (#6205) 2025-10-29 11:12:57 +11:00
WithoutPants
d70ff551d4 Replace "movie" with "group" in scene is missing criterion (#6204)
* Add support for "group" value in scene is-missing filter criterion
* Replace movie with group in scene is missing criterion
2025-10-29 11:12:42 +11:00
WithoutPants
1dccecc39c Go to list page if deleting with empty history (#6203) 2025-10-29 11:12:25 +11:00
WithoutPants
648875995c Fix play random not using effective filter (#6202) 2025-10-29 11:12:00 +11:00
WithoutPants
96b5a9448c Fix source.StashBoxEndpoint reference causing nil deref (#6201) 2025-10-29 11:11:42 +11:00
WithoutPants
fda97e7f6c Return if primary file failed to load (#6200) 2025-10-29 11:11:21 +11:00
WithoutPants
869cbd496b Update changelog 2025-10-22 12:49:27 +11:00
WithoutPants
5049d6e5c9 Fix scene list table styling issues (#6169)
* Reduce z-index of table list header
* Set better max-height for scene list table
2025-10-22 12:48:39 +11:00
WithoutPants
98df51755e Fix column layout image wall issues (#6168) 2025-10-22 12:21:04 +11:00
WithoutPants
947a17355c Fix UI loop when sorting by random without seed (#6167) 2025-10-22 11:31:42 +11:00
WithoutPants
71e4071871 Encode credentials during login (#6163) 2025-10-21 19:04:44 +11:00
WithoutPants
a6778d7d22 Add discourse links to manual 2025-10-21 10:34:02 +11:00
WithoutPants
415e88808f Codeberg weblate (#6159)
* Translated using Weblate (Bulgarian)

Currently translated at 11.3% (138 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/bg/

* Translated using Weblate (Bulgarian)

Currently translated at 22.3% (272 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/bg/

---------

Co-authored-by: theqwertyqwert <theqwertyqwert@noreply.codeberg.org>
2025-10-21 08:43:59 +11:00
WithoutPants
d0283fe330 Update changelog 2025-10-21 08:21:53 +11:00
WithoutPants
c162c3843d Add timeout to ffmpeg hardware tests (#6154) 2025-10-21 08:13:42 +11:00
theqwertyqwert
cb6c53deb5 Update marker background color logic to use primaryTag name instead of title (#6141) 2025-10-20 13:00:06 +11:00
smith113-p
97ca5a28d3 Use the merged stash IDs by default (#6152) 2025-10-20 12:59:36 +11:00
smith113-p
cee68ab87b Merge URLs when merging scenes (#6151) 2025-10-20 12:58:26 +11:00
fancydancers
c6bf20dd77 install python packages system-wide (#6120) 2025-10-20 10:55:11 +11:00
gregpetersonanon
914bbfc164 Prevent scanner from failing when reading file info (#6123) 2025-10-20 10:54:26 +11:00
feederbox826
060daef0b7 add gql interceptor note to changelog #5964 (#6148) 2025-10-17 11:53:43 +11:00
WithoutPants
de5a9129b3 Use SafeMove when moving backup database (#6147) 2025-10-17 08:17:15 +11:00
WithoutPants
13953c2fbd Codeberg weblate update (#6145)
* Translated using Weblate (Indonesian)

Currently translated at 44.8% (537 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/id/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 20.6% (247 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Japanese)

Currently translated at 83.9% (1006 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Turkish)

Currently translated at 92.2% (1105 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (French)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (German)

Currently translated at 99.9% (1204 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Norwegian Nynorsk)

Currently translated at 15.6% (188 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nn/

* Translated using Weblate (Dutch)

Currently translated at 70.8% (854 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Added translation using Weblate (Urdu)

* Translated using Weblate (Czech)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Turkish)

Currently translated at 94.5% (1139 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Catalan)

Currently translated at 37.1% (448 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ca/

* Translated using Weblate (Dutch)

Currently translated at 71.0% (856 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Vietnamese)

Currently translated at 22.5% (272 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (English (United States))

Currently translated at 28.4% (343 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/en_US/

* Translated using Weblate (Russian)

Currently translated at 95.9% (1156 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ru/

* Translated using Weblate (Japanese)

Currently translated at 84.4% (1018 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Vietnamese)

Currently translated at 34.1% (412 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Vietnamese)

Currently translated at 56.2% (678 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Urdu)

Currently translated at 0.2% (3 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ur/

* Translated using Weblate (French)

Currently translated at 100.0% (1205 of 1205 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/

* Translated using Weblate (French)

Currently translated at 100.0% (1208 of 1208 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 21.2% (257 of 1208 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1208 of 1208 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (French)

Currently translated at 100.0% (1209 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1209 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 23.2% (281 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Dutch)

Currently translated at 72.7% (880 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1209 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 99.1% (1199 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 24.3% (294 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 24.3% (294 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Latvian)

Currently translated at 11.9% (145 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/lv/

* Translated using Weblate (Romanian)

Currently translated at 36.0% (436 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ro/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1209 of 1209 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (French)

Currently translated at 100.0% (1213 of 1213 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Japanese)

Currently translated at 84.3% (1023 of 1213 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (French)

Currently translated at 100.0% (1215 of 1215 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1215 of 1215 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1215 of 1215 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (French)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Vietnamese)

Currently translated at 59.6% (725 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Vietnamese)

Currently translated at 59.6% (725 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (German)

Currently translated at 99.0% (1204 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Vietnamese)

Currently translated at 60.0% (730 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (Turkish)

Currently translated at 96.0% (1168 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Polish)

Currently translated at 82.3% (1001 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/pl/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 25.0% (304 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (German)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (English (United States))

Currently translated at 28.0% (341 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/en_US/

* Translated using Weblate (Russian)

Currently translated at 97.7% (1189 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ru/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ru/

* Translated using Weblate (Persian)

Currently translated at 2.5% (31 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fa/

* Translated using Weblate (Dutch)

Currently translated at 75.7% (921 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Vietnamese)

Currently translated at 60.2% (733 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Japanese)

Currently translated at 84.2% (1024 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 94.6% (1151 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1216 of 1216 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (French)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Added translation using Weblate (Lithuanian)

* Translated using Weblate (Korean)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Lithuanian)

Currently translated at 8.6% (105 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/lt/

* Translated using Weblate (German)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Croatian)

Currently translated at 20.6% (252 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/hr/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Vietnamese)

Currently translated at 64.5% (787 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Indonesian)

Currently translated at 47.1% (575 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/id/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Indonesian)

Currently translated at 49.6% (605 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/id/

* Translated using Weblate (Italian)

Currently translated at 75.7% (924 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/it/

* Translated using Weblate (Finnish)

Currently translated at 80.3% (979 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fi/

* Translated using Weblate (Spanish)

Currently translated at 96.8% (1180 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Urdu)

Currently translated at 0.8% (10 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ur/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1219 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Turkish)

Currently translated at 95.8% (1168 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Added translation using Weblate (Bulgarian)

* Translated using Weblate (Finnish)

Currently translated at 80.5% (982 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fi/

* Translated using Weblate (Bulgarian)

Currently translated at 4.1% (51 of 1219 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/bg/

* Correct new locale filenames

* Update language options

* Correct error in de-DE

* Filter en-US to only different strings

---------

Co-authored-by: fafafafa <fafafafa@noreply.codeberg.org>
Co-authored-by: boy3satiable <boy3satiable@noreply.codeberg.org>
Co-authored-by: ynclt <ynclt@noreply.codeberg.org>
Co-authored-by: slickdaddy <slickdaddy@noreply.codeberg.org>
Co-authored-by: doodoo <doodoo@noreply.codeberg.org>
Co-authored-by: AlpacaSerious <alpacaserious@noreply.codeberg.org>
Co-authored-by: tzuuuss <tzuuuss@noreply.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: throbbing <throbbing@noreply.codeberg.org>
Co-authored-by: youri <youri@noreply.codeberg.org>
Co-authored-by: Zesty6249 <zesty6249@noreply.codeberg.org>
Co-authored-by: Lambert99 <lambert99@noreply.codeberg.org>
Co-authored-by: NymeriaCZ <nymeriacz@noreply.codeberg.org>
Co-authored-by: yec <yec@noreply.codeberg.org>
Co-authored-by: burrisol <burrisol@noreply.codeberg.org>
Co-authored-by: Cindicent <cindicent@noreply.codeberg.org>
Co-authored-by: nitromelon <nitromelon@noreply.codeberg.org>
Co-authored-by: boxcrunch <boxcrunch@noreply.codeberg.org>
Co-authored-by: Fl0master1337 <fl0master1337@noreply.codeberg.org>
Co-authored-by: tobakumap <tobakumap@noreply.codeberg.org>
Co-authored-by: dragoncrazy2011 <dragoncrazy2011@noreply.codeberg.org>
Co-authored-by: CrypticGlycolic <crypticglycolic@noreply.codeberg.org>
Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: bwithnewcast <bwithnewcast@noreply.codeberg.org>
Co-authored-by: COTMO <cotmo@noreply.codeberg.org>
Co-authored-by: danny60718 <danny60718@noreply.codeberg.org>
Co-authored-by: noTranslator <notranslator@noreply.codeberg.org>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@noreply.codeberg.org>
Co-authored-by: noqqyg <noqqyg@noreply.codeberg.org>
Co-authored-by: DJSweder <djsweder@noreply.codeberg.org>
Co-authored-by: m4549071758 <m4549071758@noreply.codeberg.org>
Co-authored-by: lugged9922 <lugged9922@noreply.codeberg.org>
Co-authored-by: phanh <phanh@noreply.codeberg.org>
Co-authored-by: krohnoz <krohnoz@noreply.codeberg.org>
Co-authored-by: AngryPikachu_025 <angrypikachu_025@noreply.codeberg.org>
Co-authored-by: certivian <certivian@noreply.codeberg.org>
Co-authored-by: Marly21 <marly21@noreply.codeberg.org>
Co-authored-by: OtterBotSociety <otterbotsociety@noreply.codeberg.org>
Co-authored-by: Schmitd <schmitd@noreply.codeberg.org>
Co-authored-by: mmovahedi <mmovahedi@noreply.codeberg.org>
Co-authored-by: DNArjen <dnarjen@noreply.codeberg.org>
Co-authored-by: nguyenhuy158 <nguyenhuy158@noreply.codeberg.org>
Co-authored-by: furinkazan <furinkazan@noreply.codeberg.org>
Co-authored-by: Phrotan <phrotan@noreply.codeberg.org>
Co-authored-by: TWNO1 <twno1@noreply.codeberg.org>
Co-authored-by: Troink <troink@noreply.codeberg.org>
Co-authored-by: zo3n <zo3n@noreply.codeberg.org>
Co-authored-by: manhtuanphoto <manhtuanphoto@noreply.codeberg.org>
Co-authored-by: om_Yanto <om_yanto@noreply.codeberg.org>
Co-authored-by: shanpai <shanpai@noreply.codeberg.org>
Co-authored-by: Uskonalle <uskonalle@noreply.codeberg.org>
Co-authored-by: gallegonovato <gallegonovato@noreply.codeberg.org>
Co-authored-by: jirkacapek123 <jirkacapek123@noreply.codeberg.org>
Co-authored-by: theqwertyqwert <theqwertyqwert@noreply.codeberg.org>
Co-authored-by: Ricky-Tigg <ricky-tigg@noreply.codeberg.org>
2025-10-16 18:31:33 +11:00
WithoutPants
479ad49e81 Add 0.29 release notes (#6144)
* Add 0.29 release notes
* Add optional release notes to changelog entries
2025-10-16 14:45:29 +11:00
WithoutPants
ce4b86daf5 Fix tag order on details pages (#6143)
* Fix related tag order
* Fix unit tests
2025-10-16 13:15:09 +11:00
WithoutPants
0c5285c949 Add 0.29 changelog 2025-10-15 17:55:05 +11:00
WithoutPants
fbba4f06a9 Correct movies to groups in default menu items (#6140)
Fixes unnecessary config migration artifact in new systems
2025-10-15 16:53:08 +11:00
WithoutPants
e1b3b33c24 Correctly load generate options when generating from tasks page (#6139) 2025-10-15 16:52:54 +11:00
underprovisioned
eb816d2e4f Sort duplicate scene groups by total filesize descending (#6133) 2025-10-15 16:52:40 +11:00
WithoutPants
05e2fb26be Fix setup wizard issues (#6138)
* Correct paths in confirm step
* Maintain paths when going back from confirm step
2025-10-15 16:31:52 +11:00
WithoutPants
7b182ac04b Vacuum into database directory then move file if backup dir different (#6137)
If the backup directory is not the same directory as the database, then vacuum into the same directory then move it to its destination. This is to prevent issues vacuuming over a network share.
2025-10-15 16:30:06 +11:00
WithoutPants
2e8bc3536f Null check image visual_files (#6136) 2025-10-15 16:29:51 +11:00
WithoutPants
6d76fe690b Add padding to tag links (#6129) 2025-10-13 13:13:58 +11:00
WithoutPants
d3f6301101 Use natural sort for related tags (#6128) 2025-10-13 13:13:45 +11:00
WithoutPants
72c9c436be Fix groups not transferring when merging tags (#6127)
* Add test for group when merging tags
* Fix groups not reallocated when merging tags
2025-10-13 13:13:23 +11:00
fancydancers
2ed9e5332d add content-disposition filename header to streams (#6119)
* add content-disposition filename header to streams
* Fix filename generation on windows
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-10-09 13:35:11 +11:00
WithoutPants
c5bad48ece Scene list cleanup (#6104)
* Generalise and cleanup list toolbar
* Generalise ListResultsHeader
* Fix padding on sub-pages
2025-10-06 07:45:36 +11:00
WithoutPants
af76f4a24a Prevent input field from focusing on touch devices rather than mobile (#6105) 2025-10-06 07:44:59 +11:00
xtc1337
15bf28d5be Adding the ability to support different Haptic Devices (#5856)
* refactored `Interactive` class to allow more HapticDevice devices
* simplified api hooks
* update creation of `interactive` to pass `stashConfig`
* updated UIPluginApi to mention `PluginApi.InteractiveUtils`
2025-09-25 15:27:58 +10:00
sezzim
c9ca40152f Show gallery cover on the edit panel (#5935) 2025-09-25 15:26:24 +10:00
WithoutPants
724d438721 Wall item height fix (#6101)
* Fix scene wall item height with fewer items
* Fix for marker wall
* Fix for image wall
* Provide some allowance for items to go over height
2025-09-25 15:26:01 +10:00
DogmaDragon
acddf97771 Refactor issue templates: replace markdown files with YAML configurations for bug reports, feature requests (#6102) 2025-09-25 15:20:30 +10:00
WithoutPants
823ed346c1 Add separate sidebar toggle button (#6077)
* Move sidebar toggle to right. Change icon
* Show sidebar button on selection
* Fix clicking toggle cycling visibility on smaller views
* Show more tags component when cutoff == 0
* Hide filter/filter icon buttons in certain situations
* Move sidebar toggle to left on xl viewports
2025-09-24 11:27:08 +10:00
WithoutPants
3bb771a149 Add search term filter tag to scene list filter tags (#6095)
* Add search term to filter tags on scene list page

Clicking on the tag selects all on the search term input. Clicking on the x erases it.

* Ensure clear criteria maintains consistent behaviour on other pages
* Hide search term tag when input is visible
2025-09-24 10:45:09 +10:00
WithoutPants
793a5f826e Edit filter load save (#6092)
* Add load/save buttons to edit filter dialog
* Add title to save filter dialog
* Change ExistingSavedFilterList parameters
* Add title to load/save buttons
2025-09-18 12:09:19 +10:00
WithoutPants
8012f2eb8a Add search term input to edit filter dialog (#6082) 2025-09-17 15:08:47 +10:00
WithoutPants
98716d5568 Show search field always (#6079) 2025-09-17 14:41:48 +10:00
feederbox826
edcc4e8968 Fix descender line-height (#6087) 2025-09-16 14:25:22 +10:00
Otter Bot Society
12c4e1f61c Treat images with no exif metadata as well-oriented (#6006) 2025-09-09 16:48:16 +10:00
WithoutPants
cc97e96eaa Add wall zoom functionality (#6011)
* Show zoom slider when wall view active
* Add zoom functionality to scene wall
* Add zoom functionality to image wall
* Add zoom functionality to gallery wall
* Add zoom functionality for marker wall
2025-09-09 16:45:29 +10:00
WithoutPants
b1883f3df5 Add gallery link to image lightbox (#6012) 2025-09-09 16:44:51 +10:00
gregpetersonanon
fd36c0fac7 Allow scan to continue when encountering an error (#6073) 2025-09-09 15:10:13 +10:00
feederbox826
b5b207c940 remove ruby and faraday gem (#6020) 2025-09-09 15:07:00 +10:00
feederbox826
c0ba119ebf exclude empty regex exclude (#6023) 2025-09-09 15:04:39 +10:00
feederbox826
e23bdfa204 Add media hardware key support (#6031) 2025-09-09 15:03:55 +10:00
WithoutPants
14be3c24ff Revert "Search term filter tag on scene list (#5991)" (#6003)
This reverts commit 21ee88b149.
2025-07-08 13:12:46 +10:00
WithoutPants
21ee88b149 Search term filter tag on scene list (#5991)
* Add search term to filter tags on scene list page

Clicking on the tag selects all on the search term input. Clicking on the x erases it.

* Ensure clear criteria maintains consistent behaviour on other pages
2025-07-08 10:41:33 +10:00
WithoutPants
dd9a1f5ce3 Apply filter hook to subpage sidebar filters (#5995)
* Apply filterHook to sidebar filters on subpages
* Hide studio filter in studio subpage
2025-07-08 10:41:12 +10:00
WithoutPants
694675470e Map parent studio after creation (#5996) 2025-07-08 10:40:54 +10:00
WithoutPants
642b0f2291 Add missing keybinds to scene list (#5994) 2025-07-03 14:41:06 +10:00
WithoutPants
1b3a8acab2 Bring back select all/none to scene list (#5993) 2025-07-03 13:07:37 +10:00
WithoutPants
108c0c7de5 Use /plugins instead of /plugin in testReact 2025-07-03 10:40:51 +10:00
WithoutPants
dcfb3b7d37 Throw error on re-registering component only if in prod environment (#5990)
This was causing an error when hot-reloading components, meaning that the components would not be refreshed.
2025-07-02 17:07:01 +10:00
WithoutPants
d98e9c6618 Show filter tags in scene list toolbar (#5969)
* Add filter tags to toolbar
* Show overflow control if filter tags overflow
* Remove second set of filter tags from top of page
* Add border around filter area
2025-07-02 16:34:40 +10:00
QxxxGit
f01f95ddfb Organize UIPluginApi.md docs and pluginApi.d.ts (#5971)
* Organized alphabetically and removed duplicate Setting and TabTitleCounter
* Organized components alphabetically
* Add missing PerformerDetailsPanel and PerformerDetailsPanel.DetailGroup
* Add missing SceneFileInfoPanel component
* Add missing MainNavBar.MenuItems and MainNavBar.UtilityItems in docs
2025-07-01 13:48:16 +10:00
WithoutPants
3a232b1d6c Pagination styling (#5973)
* Raise pagination slightly to avoid occlusion from link bar
* Add shadow to pagination
2025-06-30 07:53:33 +10:00
WithoutPants
6f4920cb81 Update custom css links and replace plex theme link with themes link (#5976) 2025-06-30 07:53:08 +10:00
WithoutPants
61ab6ce6bd Fix funscript parsing issues (#5978)
* Accept floating point numbers for at field in funscript
* Ignore type of script version field
* Write rounded ints for csv
2025-06-30 07:52:53 +10:00
WithoutPants
7215b6e918 Ensure tmp dir is created before creating temp file (#5977) 2025-06-30 07:52:32 +10:00
WithoutPants
bd8ec8cb83 Don't update stash ids when scraping from stash-box (#5975) 2025-06-30 07:52:12 +10:00
WithoutPants
429022a468 Handle marshalling scraped movie to group (#5974) 2025-06-30 07:51:53 +10:00
WithoutPants
5323d68d3d Add graphql playground link to tools panel (#5807)
* Add graphql playground link to tools panel
* Move to separate section
2025-06-27 16:26:03 +10:00
DogmaDragon
3af472d3b2 Fix typo in Plugins documentation for clarity (#5972) 2025-06-27 12:54:44 +10:00
WithoutPants
7eff7f02d0 Add findFolder and findFolders queries to graphql schema (#5965)
* Add findFolder and findFolders queries to graphql schema
* Add zip file criterion to file and folder queries
2025-06-26 15:48:29 +10:00
WithoutPants
661d2f64bb Make path criterion default modifier includes instead of equals (#5968) 2025-06-26 15:47:45 +10:00
WithoutPants
d0a7b09bf3 Scene list toolbar (#5938)
* Add sticky query toolbar to scenes page
* Filter button accept count instead of filter
* Add play button
* Add create button functionality. Remove new scene button from navbar
* Separate toolbar into component
* Separate sort by select component
* Don't show filter tags control if no criteria
* Add utility setter methods to ListFilterModel
* Add results header with display options
* Use css for filter tag styling
* Add className to OperationDropdown and Item
* Increase size of sidebar controls on mobile
2025-06-26 09:17:22 +10:00
DogmaDragon
27bc6c8fca Update captions documentation (#5967)
* Update captions documentation to clarify file location and naming conventions
* Clarify naming convention
2025-06-26 07:33:55 +10:00
WithoutPants
704041d5e0 Add findFiles and findFile graphql queries (#5941)
* Add findFile and findFiles
* Add parent folder and zip file fields to file graphql types
* Add parent_folder, zip_file fields to Folder graphql type
* Add format to ImageFile type
* Add format filter fields to image/video file filters
2025-06-24 13:05:17 +10:00
damontecres
8d78fd682d Include searching by tag sort name (#5963) 2025-06-24 13:02:19 +10:00
WithoutPants
81c3988777 Give bottom pagination bar transparent background (#5958) 2025-06-24 13:01:28 +10:00
WithoutPants
4b5424dd51 Update manual with new patchable components 2025-06-24 08:27:41 +10:00
dogwithakeyboard
e69238307c add missing property to death date item (#5962) 2025-06-24 07:59:27 +10:00
feederbox826
019fe81de9 Update Freeones scraper from CommunityScrapers (#5956)
1b103ad2d5

Co-authored-by: feederbox826 <feederbox826@users.noreply.github.com>
2025-06-23 14:13:01 +10:00
WithoutPants
5177f71dbd Fix UI crash in performer -> gallery wall lightbox (#5947) 2025-06-23 14:12:07 +10:00
WithoutPants
497146adc5 Support patching Pagination and PaginationIndex (#5957) 2025-06-23 14:11:51 +10:00
WithoutPants
f81f60e76f Show custom fields on compact expanded details (#5946) 2025-06-20 16:04:10 +10:00
WithoutPants
849a368d3d Fix ordering of tags (#5945) 2025-06-20 16:03:56 +10:00
QxxxGit
c09913a614 Add useLightbox and useGalleryLightbox in plugin api (#5936) 2025-06-20 16:03:22 +10:00
WithoutPants
c5fe6748c0 Hide list view options popover on select (#5940) 2025-06-19 16:40:06 +10:00
WithoutPants
fe9a6d87d2 Fix filtered list toolbar overflow on mobile devices (#5933)
Scenes list page is still ugly, but that will be addressed separately.
2025-06-17 12:30:28 +10:00
WithoutPants
7d692232ed Move pagination to a sticky bottom toolbar on scenes page (#5924)
* Adjust main padding to be the same as navbar height
* Add LoadedContent component for loading and error display
* Add option for pagination popup placement
* Show results summary at top only. Add sticky bottom pagination
2025-06-17 11:00:00 +10:00
WithoutPants
a145576f39 Display mode options dropdown (#5923)
* Separate ZoomSlider into own component
* Turn ListViewOptions into dropdown
Also puts zoom slider in the dropdown
* Move ZoomSlider into separate file
* Add title
* Restyle slider
2025-06-13 11:45:10 +10:00
WithoutPants
574fd680c9 Filter performers/tags/studios list by current filter (#5920) 2025-06-13 09:07:11 +10:00
QxxxGit
e95c1bbc76 Patched AlertModal, SweatDrops, TruncatedText, BackgroundImage components (#5913)
* Patched AlertModal, SweatDrops, TruncatedText
* Patch BackgroundImage component
* Inline PatchComponent calls
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-06-11 17:32:36 +10:00
Rémi Marseault
155dbc370b fix: Prevent generating invalid link on empty scraper response (#5876) 2025-06-11 17:32:11 +10:00
philMorel
60f1ee2360 feat: Add Performers tab to Group detail page (#5895)
* Feat(#1401): Show all performers from group's scenes on group detail
* Add Groups criterion to performers
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-06-11 17:07:09 +10:00
WithoutPants
3d03072da0 Error loading plugins (#5813)
* Improve error messages when unable to contact server
* Improve error message presentation
* Catch errors when configuration can't be loaded
* Use ErrorMessage in PagedList
* Add icon to error message
2025-06-11 16:54:11 +10:00
WithoutPants
ed4d17b8f0 Scene Filter sidebar (#5714)
* Add Sidebar component
* Add PerformerQuickFilter to Scene filter sidebar
* Add other quick filters
* Add confirmVariant field to AlertModal
* Add SidebarSavedFilterList
* Add sidebar toggle button
* Add data-type attr for criterion option
* Refactor LabeledIdFilter
* Move search input into sidebar
* Save sidebar state in local forage
* Add sidebar rating filter
* Add organised filter
* Open sidebar to / key. Focus search input on sidebar open
* Blur clearable input on escape key
2025-06-11 15:55:10 +10:00
smith113-p
a91b9c4d92 Slightly simplify code after PR #5894 (#5917)
The code looks like it does because it initially used string pointers; however, the version that landed used a regular string array, so we can just the = operator.
2025-06-11 11:49:47 +10:00
QxxxGit
709fdb14de Rating system patched components (#5912) 2025-06-11 11:46:05 +10:00
CJ
46b0b8cba4 Patch CustomFields Component (#5914) 2025-06-11 11:45:03 +10:00
WithoutPants
815ce7139c Add handler for /plugin/{}/assets (#5907)
This allows for React applications to be hosted in a plugins asset directory.
2025-06-03 20:35:29 +10:00
feederbox826
358193e25e Add note for macOS restriction (#5906) 2025-06-03 10:59:10 +10:00
dependabot[bot]
4aca81ad9b Bump vite from 4.5.13 to 4.5.14 in /ui/v2.5 (#5902)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.13 to 4.5.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 4.5.14
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 10:21:47 +10:00
CJ
c66ef42480 New patchable performer page components (#5897) 2025-06-03 10:16:57 +10:00
DirtyRacer1337
d9a316d083 add explorer plugin (#5882) 2025-06-03 10:13:14 +10:00
smith113-p
96d2b36a08 Submit all scene URLs to Stashbox (#5894) 2025-06-03 10:05:43 +10:00
WithoutPants
00f5d0d984 Upgrade gqlgenc (#5901)
* Update gqlgenc
* Fix type error
* Fix package names in config
* Remove override and regenerate
* Update compiler and bump version
2025-06-03 08:55:51 +10:00
QxxxGit
044ed2708f Gallery card patched component (#5880)
* Gallery card patched component
* Define in pluginApi.d.ts
2025-06-02 17:20:34 +10:00
WithoutPants
8e697b50eb Revamp scene and marker wall views (#5816)
* Use gallery for scene wall
* Move into separate file
* Remove unnecessary class names
* Apply configuration
* Reuse styling
* Add Scene Marker wall panel
* Adjust target row height
2025-06-02 17:18:36 +10:00
DogmaDragon
5ea4c507b2 docs: Update scraper objects (#5794) 2025-06-02 17:16:42 +10:00
dogwithakeyboard
10d4fcce8d Add zoomIndex to gallery card (#5844) 2025-06-02 17:15:23 +10:00
Rémi Marseault
86848e7d70 feat(onDelete): Redirect to previous page to preserve filters (#5818) 2025-06-02 17:12:17 +10:00
dependabot[bot]
91ac2833f5 Bump vite from 4.5.11 to 4.5.13 in /ui/v2.5 (#5847)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.11 to 4.5.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 4.5.13
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 17:10:50 +10:00
DogmaDragon
8ecbf4f7e4 Update tripwire link to direct to the forum (#5885) 2025-06-02 17:07:55 +10:00
smith113-p
0bd4edd9f4 Use StashIDPill to show stash IDs in the tagger view (#5879)
* Use StashIDPill to show stash IDs in the tagger view

This is visually nicer, but more importantly, lets you see easily which stash-boxes are already associated with this scene.

* Move into separate component. Add key
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-06-02 17:07:24 +10:00
Maista
af34829f38 Stash box validation bugfix (#5831)
* Remove accidental copypaste error

The apiKey ref was accidentally associated with the max_requests_per_minute field which made the "Test Credentials" button error out every time

* Fix error messages in stash-box validation

The message from err.Error() can start with any number of errors like NetworkError
so we can check for substrings instead
2025-06-02 15:47:03 +10:00
DogmaDragon
155c4ec72a docs: Add note on Chrome 136 requirements for remote debugging (#5884) 2025-05-23 16:07:15 +03:00
DogmaDragon
26fe812be4 Remove matrix references + add community forum (#5853) 2025-05-01 11:31:56 +03:00
DogmaDragon
997e9bfa52 Update runner image (#5846) 2025-04-28 10:34:54 +03:00
DogmaDragon
d0ece86bb8 Update markdown syntax for Scrapers Development page (#5829)
* Update markdown syntax for Scrapers Development

* Fix typo
2025-04-16 08:56:21 +10:00
WithoutPants
62d7076ff3 Add missing group scraper fields (#5820) 2025-04-16 08:55:27 +10:00
WithoutPants
f9fb33e8cc Fix scene gallery viewer displaying incorrect image 2025-04-09 12:30:04 +10:00
mz28k
2375bc6cac Fix to find a match for a parent studio (#5810) 2025-04-07 14:58:59 +10:00
WithoutPants
87d01e86fd Fix range marker alignment (#5804) 2025-04-04 15:32:52 +11:00
feederbox826
e774706f43 update Dockerfile-CUDA (#5689) 2025-04-04 14:47:22 +11:00
WithoutPants
8efae13afb Revert dot marker styling changes (#5801)
* Move marker css into styles

Removes vjs-marker-dot styling, using existing vjs-marker class instead

* Revert dot marker changes
2025-04-02 16:13:54 +11:00
WithoutPants
6ed66f3275 Ignore missing scenes when submitting fingerprints (#5799) 2025-04-02 15:48:07 +11:00
WithoutPants
2eb7bde95a Fix marker preview deleted when modifying marker with duration (#5800) 2025-04-02 14:57:24 +11:00
luigi611
edbd9b69eb Partial fix for #2761 - Add reverse proxy prefix to HLS links (#5791)
* Partial fix for #2761 - Add reverse proxy prefix to HLS links
---------
Co-authored-by: Guido <guido@test.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-04-02 14:26:39 +11:00
its-josh4
db06eae7cb Sort tags by name while scraping or merging scenes (#5752)
* Sort tags by name while scraping scenes
* TagStore.All should sort by sort_name first
* Sort tag by sort name/name in TagIDSelect
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-04-02 14:26:14 +11:00
dependabot[bot]
0f2bc3e01d Bump vite from 4.5.6 to 4.5.11 in /ui/v2.5 (#5797)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.6 to 4.5.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.11/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.11/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 14:25:28 +11:00
WithoutPants
ffee4df8b7 Log to stdout on exit error (#5798) 2025-04-02 14:25:11 +11:00
dependabot[bot]
2d5160c576 Bump @babel/runtime from 7.21.0 to 7.27.0 in /ui/v2.5 (#5766)
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.21.0 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 13:28:50 +11:00
blaspheme-ship-it
3489dca83a Display tag and performer image on hover. on the scene edit page (#5739)
* add component for PerformerPopover
* show PerformerPopover for performer select values
* show TagPopover for tag select values
2025-04-02 13:27:35 +11:00
WithoutPants
1d3bc40a6b Import/export bug fixes (#5780)
* Include parent tags in export if including dependencies
* Handle uniqueness when sanitising filenames
2025-04-01 15:04:26 +11:00
bob123491234
4bfc93b7ae Add marker end seconds import/export (#5777)
* skip importing markers if scene is skipped
2025-03-28 16:50:26 +11:00
bob123491234
c0d5d1e5a7 Add tag count to studio sort whitelist (#5776) 2025-03-28 12:45:40 +11:00
WithoutPants
bac0b0a379 Refactor scene list to not use ItemList component (#5767)
* Add fields to useListSelect
* Add more utility hooks
* Remove context from FilteredListToolbar
* Refactor SceneList to not use ItemList
* Move common logic into useFilteredListHook
2025-03-28 11:59:05 +11:00
WithoutPants
d9b4e62420 Login page internationalisation (#5765)
* Load locale strings in login page
* Generate and use login locale strings
* Add makefile target
* Update workflow
* Update build dockerfiles
* Add missing default string
2025-03-27 11:56:43 +11:00
WithoutPants
c8d74f0bcf Add rate limit to stashbox connection (#5764)
* Add max requests per minute stashbox option
* Implement rate limiting
* Add requests per minute to stashbox config
* Add UI setting
2025-03-27 11:54:00 +11:00
DogmaDragon
18381664aa Update Configuration.md (#5770) 2025-03-27 11:50:14 +11:00
blaspheme-ship-it
e9a67eb51f Add IP address to login errors (#5760) 2025-03-25 13:15:10 +11:00
WithoutPants
2ec264ed62 Fix merge error 2025-03-25 11:19:14 +11:00
dependabot[bot]
e5446a2336 Bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#5754)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.1 to 4.5.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.1...v4.5.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 10:32:43 +11:00
WithoutPants
db7d45792e Refactor stashbox package (#5699)
* Move stashbox package under pkg
* Remove StashBox from method names
* Add fingerprint conversion methods to Fingerprint

Refactor Fingerprints methods

* Make FindSceneByFingerprints accept fingerprints not scene ids
* Refactor SubmitSceneDraft to not require readers
* Have SubmitFingerprints accept scenes

Remove SceneReader dependency

* Move ScrapedScene to models package
* Move ScrapedImage into models package
* Move ScrapedGallery into models package
* Move Scene relationship matching out of stashbox package

This is now expected to be done in the client code

* Remove TagFinder dependency from stashbox.Client
* Make stashbox scene find full hierarchy of studios
* Move studio resolution into separate method
* Move studio matching out of stashbox package

This is now client code responsibility

* Move performer matching out of FindPerformerByID and FindPerformerByName
* Refactor performer querying logic and remove unused stashbox models

Renames FindStashBoxPerformersByPerformerNames to QueryPerformers and accepts names instead of performer ids

* Refactor SubmitPerformerDraft to not load relationships

This will be the responsibility of the calling code

* Remove repository references
2025-03-25 10:30:51 +11:00
WithoutPants
5d3d02e1e7 Optimise card width calculation (#5713)
* Add hook for grid card width calculation
* Move card width calculation into grid instead of card

Now calculates once instead of per card

* Debounce resize observer
2025-03-25 10:28:57 +11:00
WithoutPants
2541e9d1eb Refactor login page to not include in history (#5747) 2025-03-25 10:26:31 +11:00
WithoutPants
cc6917f29d Update changelog for bugfix release 2025-03-20 09:13:19 +11:00
WithoutPants
9636ff7c16 Parse scene t value as number not int (#5744) 2025-03-20 08:29:44 +11:00
WithoutPants
81f642b8b8 Fix incorrect URL field in studio exclusions (#5743) 2025-03-20 08:29:32 +11:00
WithoutPants
6f848f7f1c Fix setFromSavedCriterion for TimestampCriterion (#5742) 2025-03-20 08:29:17 +11:00
WithoutPants
720bbcb5c0 Update changelog 2025-03-19 09:10:29 +11:00
WithoutPants
8ce31a2831 Update weblate translations (#5734)
* Translated using Weblate (Swedish)

Currently translated at 99.8% (1195 of 1197 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Dutch)

Currently translated at 70.0% (838 of 1197 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Dutch)

Currently translated at 70.0% (838 of 1197 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (1195 of 1197 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (French)

Currently translated at 99.9% (1197 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Ukrainian)

Currently translated at 87.8% (1052 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Translated using Weblate (French)

Currently translated at 100.0% (1198 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Japanese)

Currently translated at 83.6% (1002 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1198 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 19.3% (232 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (French)

Currently translated at 100.0% (1198 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1198 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Russian)

Currently translated at 96.4% (1155 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ru/

* Translated using Weblate (Catalan)

Currently translated at 37.3% (447 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ca/

* Translated using Weblate (Spanish)

Currently translated at 98.2% (1177 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Finnish)

Currently translated at 77.3% (927 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fi/

* Translated using Weblate (Romanian)

Currently translated at 33.8% (406 of 1198 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ro/

---------

Co-authored-by: AlpacaSerious <alpacaserious@noreply.codeberg.org>
Co-authored-by: youri <youri@noreply.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: doodoo <doodoo@noreply.codeberg.org>
Co-authored-by: jmels <jmels@noreply.codeberg.org>
Co-authored-by: spyder039 <spyder039@noreply.codeberg.org>
Co-authored-by: ahsibu <ahsibu@noreply.codeberg.org>
Co-authored-by: Larsluph <codeberg@larsluph.dev>
Co-authored-by: NymeriaCZ <nymeriacz@noreply.codeberg.org>
Co-authored-by: meeeh <meeeh@noreply.codeberg.org>
Co-authored-by: Adolfo Jayme Barrientos <fito@noreply.codeberg.org>
Co-authored-by: IiroS <iiros@noreply.codeberg.org>
Co-authored-by: MallV0 <mallv0@noreply.codeberg.org>
2025-03-19 08:52:49 +11:00
WithoutPants
7a4ff20d66 Remove duplicates from ScrapedTagsRow (#5733) 2025-03-19 08:04:39 +11:00
WithoutPants
daed09e487 Fix various migration issues (#5723)
* Indicate while backing up database
* Close migrate connection to db before optimising
* Don't vacuum post-migration

In most cases is probably not needed and can be an optonal user-initiated step

* Ensure connection close on NewMigrator error
* Perform post-migration using migrator connection

Flush WAL file at end of migration
2025-03-19 08:04:21 +11:00
WithoutPants
529e4f6514 Improve UI loadable components (#5732)
* Add TagLink and PerformerCard to loadableComponents
* Add coarse grain loadable components
2025-03-18 13:21:00 +11:00
javstash
6d451d52ea Add sort by scene code option (#5708) 2025-03-17 11:23:51 +11:00
CJ
4d61c88661 Patchable ExternalLinkButtons component (#5727)
* Patchable ExternalLinkButtons component
* added fontAwesomeBrands
* use ExternalLinkButtons on groups page
2025-03-17 11:20:39 +11:00
WithoutPants
bc923929bb Stash scraper scene query (#5722)
* Enable scene querying in stash scraper
* Update docs
2025-03-17 10:20:08 +11:00
feederbox826
193b175618 docker documentation update (#5721)
- use docker compose instead of deprecated docker-compose
- add note for package as docker-cli-compose
- add link for reverse proxy
- remove obselete version string

Co-authored-by: feederbox826 <feederbox826@users.noreply.github.com>
2025-03-14 16:21:38 +11:00
WithoutPants
913a58057a Fix custom field between filter tag 2025-03-12 23:41:03 +00:00
Rémi Marseault
a621514c71 Replace history when merging tag (#5712) 2025-03-13 10:05:40 +11:00
WithoutPants
c2bc31387c Fix marker validation message 2025-03-07 14:52:54 +11:00
WithoutPants
9b7e20351a Plugin api improvements (#5703)
* Add ReactSelect to PluginApi.libraries
* Make Performer tabs patchable
* Make PerformerCard patchable
* Use registration pattern for HoverPopover, TagLink and LoadingIndicator

Initialising the components map to include these was causing an initialisation error.

* Add showZero property to PopoverCountButton
* Make TagCard patchable
* Make ScenePage and ScenePlayer patchable
* Pass properties to container components
* Add example for scene tabs
* Make FrontPage patchable
* Add FrontPage example
2025-03-05 14:04:12 +11:00
dogwithakeyboard
df5566771a Performer select calculated ages (#5110)
* Change wording of performer age at production

The Performer card had "x years old in this scene", regardless of what sort of media it was attached to. I have made both strings "x [years old] at production instead.
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-03-05 13:30:59 +11:00
WithoutPants
cbcc1994e8 Weblate translation update (#5698)
* Translated using Weblate (French)

Currently translated at 100.0% (1193 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1193 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Turkish)

Currently translated at 92.6% (1105 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1193 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (German)

Currently translated at 100.0% (1193 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1193 of 1193 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (English)

Currently translated at 100.0% (1194 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/en/

* Translated using Weblate (English (United States))

Currently translated at 2.0% (24 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/en_US/

* Translated using Weblate (Persian)

Currently translated at 0.3% (4 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fa/

* Translated using Weblate (Finnish)

Currently translated at 77.3% (924 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fi/

* Translated using Weblate (French)

Currently translated at 99.8% (1192 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.8% (1192 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (French)

Currently translated at 100.0% (1194 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1194 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Turkish)

Currently translated at 92.3% (1103 of 1194 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Revert "Translated using Weblate (English)"

This reverts commit 037755e80d.

* Revert "Translated using Weblate (English (United States))"

This reverts commit c71d87c866.

* Revert "Translated using Weblate (Chinese (Simplified Han script))"

This reverts commit 95f9ba0490.

---------

Co-authored-by: doodoo <doodoo@users.noreply.translate.codeberg.org>
Co-authored-by: AlpacaSerious <alpacaserious@users.noreply.translate.codeberg.org>
Co-authored-by: slickdaddy <slickdaddy@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: upstairs <upstairs@users.noreply.translate.codeberg.org>
Co-authored-by: Zesty6249 <zesty6249@users.noreply.translate.codeberg.org>
Co-authored-by: scottjwalter <scottjwalter@users.noreply.translate.codeberg.org>
Co-authored-by: Ricky-Tigg <ricky-tigg@users.noreply.translate.codeberg.org>
2025-03-04 14:03:15 +11:00
WithoutPants
bfdc4bac59 Add changelog for 0.28 2025-03-04 12:30:09 +11:00
CJ
a3f8c36536 Add zoom slider to other grid views (#4674)
* bring zoom slider to other list views
* updated 0 index to scale more proportionally
2025-03-04 11:56:59 +11:00
WithoutPants
0f32311f6e Wrap overflowing setting values
Long strings were pushing the edit buttons out of view
2025-03-04 09:50:39 +11:00
WithoutPants
fdb2dd9a8b Use existing formats for saved filters (#5697)
* Use existing formats for saved filters
* Fix date criterion marshalling
2025-03-04 09:26:46 +11:00
WithoutPants
ea5073fef4 Fix panic when no performer filter passed to FindPerformers 2025-03-04 09:12:40 +11:00
WithoutPants
ce2d779dbc Add FileSize component and refactor file size rendering in various components (#5695) 2025-03-03 18:38:19 +11:00
DogmaDragon
a391fa4345 Fix code comment in config.go --skip-ci (#5691) 2025-03-01 16:40:25 +11:00
WithoutPants
23e36b12fe Clear markers on unmount (#5678) 2025-02-28 18:22:13 +11:00
WithoutPants
59014f14ca Revert "Add docker labels to have update tools be able to pull changelog/rele…" (#5688)
This reverts commit 661e9eba51.
2025-02-28 17:56:32 +11:00
dumdum7
bf3a0e7944 Handy integration improvements (#5576)
* Use playing event instead of play
* Remove unnecessary ensurePlaying() from timeupdate listener

Eliminates redundant API calls by only relying on playing and pause events. Handles edge cases for playback before script upload completion.

* Remove unnecessary video seeking event listener

We don't need it anymore, listening for playing and pause events is enough.

* Send second play event after a play event to adjust for video player issues
* Fix script being paused and played after 10 seconds because of activity tracker dependency change
2025-02-28 14:11:50 +11:00
echo6ix
5f595f8ca7 Update KeyboardShortcuts.md (#5615)
* Update KeyboardShortcuts.md

- Added table of shortcuts for Image page
- Reorganized a few tables to include sub-headings
- Renamed `Edit Scene tab [...]` heading to `Scene Edit tab [...]` for logical consistency with other headings
- Moved Scene page rating shortcuts from vestigial location in *Edit Scene* section to global Scene page section

* Update edit header to be consitant with the others

---------

Co-authored-by: DogmaDragon <103123951+DogmaDragon@users.noreply.github.com>
2025-02-28 13:42:22 +11:00
fume8866
4d447c3340 update merged performer upon batch update (#5664)
* update merged performer upon batch update
* Handle aliases and name for merged performer
* Refactor merge performer code

Log when merging performers
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-27 14:23:48 +11:00
Gavin Mogan
661e9eba51 Add docker labels to have update tools be able to pull changelog/release notes (#4923)
* Add docker labels to have update tools be able to pull changelog/release notes

For example https://docs.renovatebot.com/modules/datasource/docker/

but other tools will pull those same labels

* Add stash version to docker push

---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-27 14:23:24 +11:00
bunkmate5127
b49157f968 Fix erroneous filesize units (#4266)
Units are all calculated in the base 2 variants (as they should be), but were all named, and carry the units for, the base 10 variants.
2025-02-27 14:05:32 +11:00
javstash
7f58309143 Preserve JAV title in Tagger (#5645)
* Preserve JAV title in Tagger
* Styling and documentation
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-27 14:02:17 +11:00
RampantDespair
4f45ea8e7d Added f4v to default video extensions (#5624) 2025-02-27 10:45:43 +11:00
WithoutPants
ccf79d077f Use tag exclusions when identifying scenes (#5686)
* Move tag exclusion code back into scraper package

Reverts #2391

* Rearrange stash box client code
* Filter excluded tags in stashbox queries

Re-application of fix for #2379
2025-02-27 09:07:02 +11:00
WithoutPants
f23450c380 Fix custom field numbers not filtering correctly (#5685) 2025-02-26 14:04:51 +11:00
skier233
f65976cf4d fix point markers ui bug (#5684) 2025-02-26 09:18:53 +11:00
WithoutPants
b8af147a8d Initialise UpdatedAt for stash ids (#5680)
* Initialise imported zero time to epoch time

Fixes null time error after importing stash id without updatedAt set

* Update unit tests
2025-02-26 08:03:35 +11:00
WithoutPants
1e05766571 Fix scraping multiple URLs (#5677)
* Hack fix for scraping URLs field
* Rewrite apply function using known value types
2025-02-26 08:03:08 +11:00
WithoutPants
587fd9e6b8 Fix image title not appearing in lightbox (#5675) 2025-02-24 17:55:51 +11:00
WeedLordVegeta420
e97f647a43 Add Image Scraping (#5562)
Co-authored-by: keenbed <155155956+keenbed@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-24 16:38:14 +11:00
WithoutPants
b6ace42973 Custom fields filter UI (#5632)
* Refactor criteria and criterion options
* Add custom fields filtering in UI
2025-02-24 14:32:53 +11:00
Ceri Loosley
46d424fbaf UI: Various pwa manifests fixes (#5669)
* UI: Manifest changes and new square SVG to be used by PWA's
* UI: Fix manifest to include smaller sizes
* Make a maskable icon with a background so it can be seen on most platforms
* UI: Anti-Flashbang

Make the background colour the same as the background as stash
2025-02-24 14:30:53 +11:00
skier233
d915787840 Fix markers ui bug (#5671)
* Move loadMarkers to separate callback
* Remove async from findColors
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-24 14:29:59 +11:00
DogmaDragon
57e044e689 fix: broken links on README.md 2025-02-19 22:57:35 +02:00
Maista
3f90e57861 Show scene cover image in player preview (#5666)
This was accidentally removed in #5633
2025-02-19 08:25:36 +11:00
WithoutPants
0296b63be5 Fix lint error 2025-02-18 18:18:48 +11:00
DogmaDragon
e041ad190f Fix variables for help links in Stash Setup Wizard (#5661)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-18 17:10:34 +11:00
skier233
3ea49c6c2e Add UI for Markers with end seconds on scene player. (#5633)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-18 17:10:15 +11:00
WithoutPants
c8032f04fa Don't set image.title to file basename in graphql (#5658)
* Don't set image title to filename in graphql
* Remove deprecated files field from image fragments
2025-02-18 16:32:33 +11:00
WithoutPants
50a900e83c Show correct image in lightbox (#5659) 2025-02-18 16:32:13 +11:00
InfiniteStash
638398808b Add death date fetching from stash-box (#5653) 2025-02-11 15:09:50 +11:00
stg-annon
d2daf6c69f Add Sort Name to Tags (#5531)
* override "name" sort with COALESCE
* tag sort_name frontend

adds `data-sort-name` attribute to tag links prioritizes sort_name value but will default to tag name if not present in the same way that COALESCE will prioritize the same values in the same way
* add sort_name filter, update locale per request

* Include sort name in anonymiser
* Add import/export support
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-02-11 08:17:21 +11:00
81hvac1k02
dd40c07a6d removed un-necesarry dependancy (#5641) 2025-02-06 13:35:08 +11:00
WithoutPants
d95e35783a Weblate translation update (#5636)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Added translation using Weblate (Slovak)

* Translated using Weblate (Slovak)

Currently translated at 1.9% (23 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sk/

* Added translation using Weblate (Vietnamese)

* Translated using Weblate (Vietnamese)

Currently translated at 9.4% (111 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (German)

Currently translated at 87.4% (1028 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (French)

Currently translated at 100.0% (1179 of 1179 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1179 of 1179 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1181 of 1181 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1181 of 1181 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1181 of 1181 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (French)

Currently translated at 100.0% (1181 of 1181 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1181 of 1181 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Czech)

Currently translated at 99.3% (1175 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1183 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (French)

Currently translated at 100.0% (1183 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Polish)

Currently translated at 83.8% (992 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/pl/

* Translated using Weblate (Vietnamese)

Currently translated at 14.1% (167 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/vi/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1183 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Turkish)

Currently translated at 72.6% (859 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Turkish)

Currently translated at 82.3% (974 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (German)

Currently translated at 87.1% (1031 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Dutch)

Currently translated at 69.7% (825 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Turkish)

Currently translated at 85.4% (1011 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Dutch)

Currently translated at 70.4% (834 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nl/

* Translated using Weblate (Turkish)

Currently translated at 87.2% (1032 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Turkish)

Currently translated at 87.2% (1032 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Norwegian Nynorsk)

Currently translated at 14.7% (174 of 1183 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nn/

* Translated using Weblate (French)

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Korean)

Currently translated at 99.5% (1179 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Turkish)

Currently translated at 91.0% (1078 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Turkish)

Currently translated at 93.3% (1105 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (French)

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Ukrainian)

Currently translated at 18.7% (222 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 29.3% (348 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 60.3% (714 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1184 of 1184 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (German)

Currently translated at 87.5% (1037 of 1185 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (English (United States))

Currently translated at 1.3% (16 of 1185 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/en_US/

* Translated using Weblate (Spanish)

Currently translated at 97.6% (1157 of 1185 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (French)

Currently translated at 100.0% (1192 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1192 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Czech)

Currently translated at 99.9% (1191 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (Ukrainian)

Currently translated at 88.2% (1052 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1192 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (German)

Currently translated at 100.0% (1192 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (1192 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Slovak)

Currently translated at 2.4% (29 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sk/

* Translated using Weblate (Japanese)

Currently translated at 83.8% (999 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Latvian)

Currently translated at 9.4% (113 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/lv/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 19.0% (227 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Translated using Weblate (Turkish)

Currently translated at 92.7% (1105 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/tr/

* Translated using Weblate (Polish)

Currently translated at 83.8% (1000 of 1192 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/pl/

* Add instructions to merge codeberg translations

* Rename locale files to correct format

* Add new locales to dropdown list

* Fix error when selecting nn-NO locale

---------

Co-authored-by: AlpacaSerious <AlpacaSerious@users.noreply.translate.codeberg.org>
Co-authored-by: Zesty6249 <Zesty6249@users.noreply.translate.codeberg.org>
Co-authored-by: rodopd <rodopd@users.noreply.translate.codeberg.org>
Co-authored-by: namphongbody <namphongbody@users.noreply.translate.codeberg.org>
Co-authored-by: NymeriaCZ <NymeriaCZ@users.noreply.translate.codeberg.org>
Co-authored-by: upstairs <upstairs@users.noreply.translate.codeberg.org>
Co-authored-by: doodoo <doodoo@users.noreply.translate.codeberg.org>
Co-authored-by: TWNO1 <TWNO1@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: Larsluph <Larsluph@users.noreply.translate.codeberg.org>
Co-authored-by: danny60718 <danny60718@users.noreply.translate.codeberg.org>
Co-authored-by: k1ngt0ng <k1ngt0ng@users.noreply.translate.codeberg.org>
Co-authored-by: slickdaddy <slickdaddy@users.noreply.translate.codeberg.org>
Co-authored-by: Vistaus <Vistaus@users.noreply.translate.codeberg.org>
Co-authored-by: throbbing <throbbing@users.noreply.translate.codeberg.org>
Co-authored-by: yec <yec@users.noreply.translate.codeberg.org>
Co-authored-by: orders-pawl <orders-pawl@users.noreply.translate.codeberg.org>
Co-authored-by: Mila_42 <Mila_42@users.noreply.translate.codeberg.org>
Co-authored-by: murgleburgle <murgleburgle@users.noreply.translate.codeberg.org>
Co-authored-by: DJSweder <DJSweder@users.noreply.translate.codeberg.org>
Co-authored-by: debate <debate@users.noreply.translate.codeberg.org>
Co-authored-by: abev66 <abev66@users.noreply.translate.codeberg.org>
Co-authored-by: pipo <pipo@users.noreply.translate.codeberg.org>
Co-authored-by: ikayaki <ikayaki@users.noreply.translate.codeberg.org>
Co-authored-by: Marky05 <marky05@users.noreply.translate.codeberg.org>
Co-authored-by: lexiconi <lexiconi@users.noreply.translate.codeberg.org>
Co-authored-by: LauraS <lauras@users.noreply.translate.codeberg.org>
2025-01-30 16:38:44 +11:00
dependabot[bot]
3078cb39c1 Bump golang.org/x/net from 0.30.0 to 0.33.0 (#5634)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.30.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.30.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-30 14:14:33 +11:00
dependabot[bot]
5a8725b233 Bump vite from 4.5.5 to 4.5.6 in /ui/v2.5 (#5621)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.5 to 4.5.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.6/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-30 13:58:32 +11:00
dumdum7
b0a10399d7 Automatically resync Handy (#5581)
* Resync Handy every hour
* Don't try to upload script after resync if Handy is disconnected
2025-01-30 13:54:46 +11:00
dumdum7
9f7d00d83f Don't set interactiveReady when initializing player (#5578) 2025-01-30 13:51:46 +11:00
damontecres
b30bd8d2fe Find scene markers by ID (#5567) 2025-01-30 13:41:09 +11:00
stg-annon
8bacaa17f4 Use Marker endSeconds value when generating previews (#5542)
* generate marker previews using endSeconds value
* Limit marker preview duration to 20 seconds max
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-01-30 13:40:08 +11:00
0x60B2
4d43763a39 feat: Add ETA for tasks (#5535)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2025-01-30 13:28:40 +11:00
bayured
44d764d832 Update PerformerModal.tsx to fix aliases exclusions (#5566) 2025-01-30 13:24:45 +11:00
dependabot[bot]
726296bb54 Bump nanoid from 3.3.6 to 3.3.8 in /ui/v2.5 (#5552)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.6 to 3.3.8.
- [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.3.6...3.3.8)

---
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>
2025-01-30 13:19:56 +11:00
DogmaDragon
4ed522c5f8 Correct syntax (#5586) 2025-01-29 14:55:59 +11:00
echo6ix
b7592374aa Update Tasks.md (#5603)
* Update Tasks.md

Denoted which task items are only accessible in advanced mode.

* Add note to transcodes

---------

Co-authored-by: DogmaDragon <103123951+DogmaDragon@users.noreply.github.com>
2025-01-29 14:55:21 +11:00
DogmaDragon
077cd774f3 docs: clarify regex case sensitivity (#5607) 2025-01-29 14:54:59 +11:00
DogmaDragon
b5cb52bb5e docs: add note about lack of SOCKS5 support (#5630) 2025-01-29 14:53:44 +11:00
Ikko Eltociear Ashimine
0621d87133 docs: update ScraperDevelopment.md (#5529) 2024-12-04 15:57:57 +11:00
WithoutPants
cacfe5a268 Add PerformerSelect as loadable component (#5528)
* Add PerformerSelect to loadable components
* Add PerformerSelect to example plugin
2024-12-04 14:15:32 +11:00
WithoutPants
8c8be22fe4 Performer custom fields (#5487)
* Backend changes
* Show custom field values
* Add custom fields table input
* Add custom field filtering
* Add unit tests
* Include custom fields in import/export
* Anonymise performer custom fields
* Move json.Number handler functions to api
* Handle json.Number conversion in api
2024-12-03 13:49:55 +11:00
WithoutPants
a0e09bbe5c Fix UI plugin race conditions (#5523)
* useScript to return load state of scripts
* Wait for scripts to load before rendering

Also moves plugin code into plugins.tsx
2024-12-03 08:02:46 +11:00
WithoutPants
4be793d4b3 Fix scraped tags issues (#5522)
* Fix display of matched scraped tags
* Fix create new scraped tag not updating field correctly
2024-12-03 08:02:29 +11:00
WithoutPants
60bb6bf50b Hide legacy groups criterion option (#5521) 2024-12-03 08:02:13 +11:00
dogwithakeyboard
7f8349469a Scene Marker grid view (#5443)
* add bulk delete mutation
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-11-29 17:02:20 +11:00
dogwithakeyboard
6ad0951878 Scene Marker duration filter and sort (#5472)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-11-29 16:28:10 +11:00
dogwithakeyboard
e097f2b3f4 Tagger preview scrubber and thumbnail (#5507) 2024-11-28 09:31:37 +11:00
dependabot[bot]
3c81d3b154 Bump cross-spawn from 7.0.3 to 7.0.6 in /ui/v2.5 (#5486)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-28 09:28:16 +11:00
DogmaDragon
ef2231f97b Update tripwire link (#5493) 2024-11-22 08:28:09 +11:00
WithoutPants
f81202660c Validate tagger blacklist entries (#5497)
* Don't let invalid tagger regex crash UI
* Validate blacklist entries and show errors
2024-11-22 08:27:41 +11:00
WithoutPants
6c5bf5f052 Convert json numbers to numbers (#5496) 2024-11-22 08:27:23 +11:00
WithoutPants
5f690d96bd Fix stash scraper errors and add apikey field (#5474)
* Use hasura/go-graphql-client instead of shurcooL version
* Fix graphql query errors
* Support setting api key for stash server
2024-11-13 10:14:55 +11:00
WithoutPants
64fed3553a Lint fixes (#5476)
* Fix lint errors
* Bump lint action version
2024-11-13 09:47:29 +11:00
WithoutPants
a18c538c1f Maintain saved filters in full export/import (#5465)
* Remove ellipsis from full export button
2024-11-12 16:59:28 +11:00
dependabot[bot]
41d1b45fb9 Bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#5451)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-08 14:54:22 +11:00
WithoutPants
602f95dd29 Fix video files with identical phashes being merged during scan (#5461)
* Change Fingerprints.Remove to return new instead of mutate current
* Match only by oshash and md5 when merging scenes during scan
2024-11-07 14:29:26 +11:00
WithoutPants
2a454e5a1e Fix scraped tag name being used in matched scraped tags field (#5462) 2024-11-07 14:29:13 +11:00
WithoutPants
a100f8ffc8 Refactor setup wizard to fix text input (#5459) 2024-11-07 13:16:28 +11:00
MinasukiHikimuna
527c282b92 Setting marker end time with clock button uses full precision (#5437) 2024-11-03 15:02:52 +11:00
WithoutPants
e8125d08db Sub second marker timestamp precision (#5431)
* Allow DurationInput to accept/format timestamps with milliseconds
* Get current frame at sub-second precision
2024-11-02 14:59:54 +11:00
MinasukiHikimuna
0d40056f8c Markers can have end time (#5311)
* Markers can have end time

Other metadata sources such as ThePornDB and timestamp.trade support end times for markers but Stash did not yet support saving those. This is a first step which only allows end time to be set either via API or via UI. Other aspects of Stash such as video player timeline are not yet updated to take end time into account.

- User can set end time when creating or editing markers in the UI or in the API.
- End time cannot be before start time. This is validated in the backend and for better UX also in the frontend.
- End time is shown in scene details view or markers wall view if present.
- GraphQL API does not require end_seconds.
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-11-02 11:55:48 +11:00
Ian McKenzie
180a0fa8dd Add updated_at field to stash_id's (#5259)
* Add updated_at field to stash_id's
* Only set updated at on stash ids when actually updating in identify

---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-10-31 15:56:16 +11:00
InfiniteStash
b1d5dc2a0e Fix scraping stash-box performers with null birthdates (#5428) 2024-10-31 11:55:58 +11:00
WithoutPants
89f539ee24 Upgrade gqlgenc and regenerate stash-box client (#5391)
* Upgrade gqlgenc and regenerate stash-box client
* Fix go version
* Don't generate resolvers
* Bump go version in compiler image. Bump freebsd version
2024-10-29 17:35:17 +11:00
WithoutPants
f949fab231 Move modifiers into selectable options (#5203) 2024-10-29 14:17:46 +11:00
WithoutPants
edb66bd4e4 Remove unnecessary scroll to top on mount for top level query pages (#5288) 2024-10-29 13:52:17 +11:00
randemgame
1b7e729750 Update Scenes' 'Updated At' Date on Heatmap Gen & Expand Error Log (#5401)
* Update Scenes' 'Updated At' Date on Heatmap Gen & Expand Error Log

The UpdatedAt field of a scene is now correctly updated after Generate Task is run and a heatmap linked to a scene.. I thought it made more sense here in the generate heatmap compared to scan.go, owing to funscript data not being tracked/stored in a typical sense with the scan message "updating metadata".
I used a simplified error messaging as I did not think it was critcal but I do not know if did not use the correct code structure
If updating the UpdatedAt field should be done there when the file is marked as interactive I can try and do that?
This would fix this long-standing issue https://github.com/stashapp/stash/issues/3738

The error message change is useful as I could not tell which scripts were causing errors before but now it is clear in the logs

* Use single transaction

---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-10-29 12:19:49 +11:00
CJ
7fb8f9172e Group details: Flippable images in expanded view. (#5367)
* flippable images in expanded view

* adjust table title width

* cleanup

* eliminate bounce and other improvements

* expand support to non full-width option
2024-10-29 11:40:46 +11:00
CJ
069a4b1f80 show/hide details via CSS rather than Javascript (#5396) 2024-10-29 11:35:58 +11:00
its-josh4
c6bcdd89be Use slices package from the stdlib when possible (#5360)
* Use slices from the stdlib when possible

* Add some unit tests

* More small tweaks + add benchmark func
2024-10-29 11:26:23 +11:00
dependabot[bot]
093de3bce2 Bump rollup from 3.29.4 to 3.29.5 in /ui/v2.5 (#5305)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.4 to 3.29.5.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.4...v3.29.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 11:11:30 +11:00
dependabot[bot]
8c5ebf3797 Bump dset from 3.1.2 to 3.1.4 in /ui/v2.5 (#5258)
Bumps [dset](https://github.com/lukeed/dset) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/lukeed/dset/releases)
- [Commits](https://github.com/lukeed/dset/compare/v3.1.2...v3.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 11:09:41 +11:00
dependabot[bot]
33e46bad64 Bump path-to-regexp from 1.8.0 to 1.9.0 in /ui/v2.5 (#5257)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 11:06:58 +11:00
dependabot[bot]
eca41dc7b4 Bump vite from 4.5.3 to 4.5.5 in /ui/v2.5 (#5270)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.3 to 4.5.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.5/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.5/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 11:03:43 +11:00
feederbox826
33ca4f8887 remove bencoder.pyx, update vips (#5416)
* remove bencoder.pyx
* revert vips downgrade
---------
Co-authored-by: feederbox826 <feederbox826@users.noreply.github.com>
2024-10-29 10:14:52 +11:00
WithoutPants
76648fee66 Update changelog for patch release 2024-10-16 08:08:37 +11:00
WithoutPants
6d07ecf751 More scene player bug fixes (#5379)
* Don't play video when seeking non-started video
* Set initial time on load instead of play
* Continue playing from current position when switching sources on error
* Remove unnecessary ref
2024-10-15 16:03:56 +11:00
WithoutPants
5283eb8ce3 Fix duplicate items appearing in selected list (again) (#5377)
* Fix duplicate detection in useListSelect
* Prevent double invocation of select handler
2024-10-15 14:29:29 +11:00
Arshad
32c48443b5 adding exists check before dropping constraints (#5363)
Co-authored-by: Arshad Khan <arshad@Arshads-MacBook-Air-2.local>
2024-10-15 13:10:47 +11:00
WithoutPants
ad00bee393 Update changelog for patch 2024-10-10 11:53:22 +11:00
WithoutPants
a54996d8a2 Weblate translation update (#5359)
* Translated using Weblate (Korean)

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Hungarian)

Currently translated at 41.9% (493 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/hu/

* Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (French)

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Hungarian)

Currently translated at 43.7% (514 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/hu/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1175 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/cs/

* Translated using Weblate (German)

Currently translated at 87.2% (1025 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 18.7% (220 of 1175 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

---------

Co-authored-by: yec <yec@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: tozoktala <tozoktala@users.noreply.translate.codeberg.org>
Co-authored-by: zdh <zdh@users.noreply.translate.codeberg.org>
Co-authored-by: doodoo <doodoo@users.noreply.translate.codeberg.org>
Co-authored-by: NymeriaCZ <NymeriaCZ@users.noreply.translate.codeberg.org>
Co-authored-by: augeee <augeee@users.noreply.translate.codeberg.org>
Co-authored-by: noTranslator <noTranslator@users.noreply.translate.codeberg.org>
2024-10-10 11:52:11 +11:00
WithoutPants
b6db4c31ca Prevent duplicate entries in selection list (#5358) 2024-10-10 10:54:39 +11:00
WithoutPants
f82e24762b Add blobs location to env binds (#5350) 2024-10-07 11:20:29 +11:00
WithoutPants
35b74be585 Restore persistence in selection when paging (#5349) 2024-10-07 11:20:20 +11:00
WithoutPants
7199d2b5ac Handle legacy scene movies criterion in saved filters (#5348) 2024-10-07 10:19:36 +11:00
WithoutPants
4697271294 Scene player fixes and improvements (#5340)
* Don't log context canceled error during live transcode
* Pause live transcode if still scrubbing
* Debounce loading live transcode source to avoid multiple ffmpeg instances
* Don't start from start or resume time if seeking before playing
* Play video when seeked before playing
2024-10-07 09:00:49 +11:00
forcalas
3e4515e62a Add Open Container Image annotations as labels to Docker image (#5323) 2024-10-03 12:51:07 +10:00
WithoutPants
58c58beb4a Fix match studio/performer links in performer view (#5337) 2024-10-03 12:50:46 +10:00
damontecres
f05518860f Add include_sub_groups message ID (#5318) 2024-10-03 12:31:43 +10:00
WithoutPants
9b567fa6f4 Exclude null values from image studio id index (#5335) 2024-10-03 11:53:29 +10:00
WithoutPants
c92de09ece Fix rating display in filter tags (#5334) 2024-10-03 11:26:18 +10:00
huochexizhan
9765b6d50e fix: fix slice init length (#5327) 2024-10-02 16:23:10 +10:00
WithoutPants
c6c3754f02 Fix panic when deleting image with no files (#5328) 2024-10-02 12:19:13 +10:00
WithoutPants
76a5b2a06a Fix UI error when image has no files (#5325) 2024-10-02 09:58:48 +10:00
WithoutPants
93a2ee1277 Fix page > total redirecting to first page instead of last (#5321) 2024-09-30 17:13:57 +10:00
WithoutPants
be6431ac13 Fix parent/child tag sort order (#5320) 2024-09-30 17:13:45 +10:00
WithoutPants
4dd8dd948e Refresh URL if random seed set (#5319) 2024-09-30 16:44:59 +10:00
Stephan
e253ba71f8 Update README.md (#5309)
Indicate dropped support for old Windows versions and indicate minimal Windows version required from 0.27.0
2024-09-30 15:39:57 +10:00
CJ
30fc2d1209 fix link menu issues (#5310) 2024-09-30 15:12:22 +10:00
WithoutPants
cef5b46f93 Fix merge dialog select boxes display issue (#5299) 2024-09-25 14:04:42 +10:00
WithoutPants
c45ae068fc Weblate translation update (#5289)
* Translated using Weblate (Korean)

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ko/

* Translated using Weblate (German)

Currently translated at 87.2% (1024 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Estonian)

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

---------

Co-authored-by: yec <yec@users.noreply.translate.codeberg.org>
Co-authored-by: TheJojonas <TheJojonas@users.noreply.translate.codeberg.org>
Co-authored-by: Zesty6249 <Zesty6249@users.noreply.translate.codeberg.org>
2024-09-23 11:56:31 +10:00
WithoutPants
a20fbe33c0 Fix tag select breaking layout 2024-09-23 10:28:43 +10:00
WithoutPants
82f4a8f671 Fix number field render 2024-09-23 10:14:29 +10:00
WithoutPants
33050f700e Prevent mouse wheel window scrolling on other number fields (#5283) 2024-09-22 15:24:54 +10:00
WithoutPants
4ad0241c53 Update changelog 2024-09-22 14:04:26 +10:00
WithoutPants
7e8c764dc7 Fix migrations not using tx (#5282) 2024-09-22 14:03:54 +10:00
dogwithakeyboard
fd9e4b3ec2 add table alias to group scene sort (#5279) 2024-09-22 13:20:33 +10:00
DogmaDragon
3abdcbee6f Replace movie with group (#5280) 2024-09-22 13:18:12 +10:00
WithoutPants
476688c84d Database connection pool refactor (#5274)
* Move optimise out of RunAllMigrations
* Separate read and write database connections
* Enforce readonly connection constraint
* Fix migrations not using tx
* #5155 - allow setting cache size from environment
* Document new environment variable
2024-09-20 12:56:26 +10:00
WithoutPants
7152be6086 Weblate translation update (#5271)
* Translated using Weblate (French)

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (German)

Currently translated at 87.8% (1017 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 89.3% (1034 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 89.3% (1034 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 95.5% (1106 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (French)

Currently translated at 100.0% (1157 of 1157 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1161 of 1161 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1161 of 1161 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (French)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (German)

Currently translated at 87.2% (1023 of 1172 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1172 of 1172 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1172 of 1172 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1172 of 1172 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (French)

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hant/

* Translated using Weblate (German)

Currently translated at 87.2% (1024 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Spanish)

Currently translated at 98.5% (1157 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Japanese)

Currently translated at 84.3% (990 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/ja/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1174 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Ukrainian)

Currently translated at 16.6% (195 of 1174 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/uk/

---------

Co-authored-by: Larsluph <Larsluph@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wql219@users.noreply.translate.codeberg.org>
Co-authored-by: AlpacaSerious <AlpacaSerious@users.noreply.translate.codeberg.org>
Co-authored-by: mrtuxa <mrtuxa@users.noreply.translate.codeberg.org>
Co-authored-by: Lytel <Lytel@users.noreply.translate.codeberg.org>
Co-authored-by: doodoo <doodoo@users.noreply.translate.codeberg.org>
Co-authored-by: Lucqqq <Lucqqq@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: lurch <lurch@users.noreply.translate.codeberg.org>
Co-authored-by: miamoreau <miamoreau@users.noreply.translate.codeberg.org>
Co-authored-by: ikayaki <ikayaki@users.noreply.translate.codeberg.org>
Co-authored-by: jc_back <jc_back@users.noreply.translate.codeberg.org>
2024-09-18 14:38:56 +10:00
WithoutPants
e4ef14e830 Fix preview scrubber touch issues (#5267) 2024-09-16 16:30:16 +10:00
Ian McKenzie
f543046349 Update upload-artifact action to pass build (#5260) 2024-09-16 10:12:09 +10:00
WithoutPants
c9f76a01c5 Add UI option for rescan scan option (#5254) 2024-09-11 17:29:27 +10:00
WithoutPants
5c4bf4ecdf Add portals for selects in dialogs (#5253) 2024-09-11 16:12:18 +10:00
WithoutPants
17be7e97d3 Emit error in SafeMove if remove from source fails (#5251) 2024-09-11 14:29:16 +10:00
WithoutPants
71e39e5cb8 Default database backup to same directory as database (#5250) 2024-09-11 14:02:00 +10:00
WithoutPants
a17199ba21 Handle symlink zip files (#5249) 2024-09-11 13:58:02 +10:00
WithoutPants
d1c207e40b Rename movies to groups in menu items in 65 post-migration (#5247)
* Only backup config file if needed in 58 migration
* Change movies to groups in menu items in 65 post-migration
2024-09-11 13:39:46 +10:00
GlitchGal
129dd0ffcc ImageDetailPanel Patch Component (#5245) 2024-09-11 11:56:17 +10:00
WithoutPants
a3838734c5 Set max-height for all modals (#5242) 2024-09-11 11:55:06 +10:00
WithoutPants
b897de3e5e Fix hover scrubber error in Firefox (#5243) 2024-09-11 11:50:40 +10:00
WithoutPants
5407596e0d Anonymise missing fields (#5244)
* Anonymise missing fields:
- galleries.photographer
- performers.disambiguation
- gallery_urls

* Anonymise captions and saved filters
2024-09-11 11:50:27 +10:00
WithoutPants
f7a164ffe5 Fix performer disambiguation styling in select (#5246) 2024-09-11 11:46:41 +10:00
Gykes
653cd16eb2 Updating Reload Scrapers formatting (#5235)
Per convo with people on Discord. I have updated the Reload Scrapers UI. It now adds a button if the filter box appears and then the button extends and takes up the whole space if the filter box does not exist.
---------
Co-authored-by: CJ <tedabed@gmail.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-09-10 14:54:15 +10:00
Gykes
a2153ced52 Bottom Page-Count button causes scenes page to reset to top (#5241)
I think was was happening is the browser was trying to do too much at once (Rendering the popup and focusing the input simultaneously). I believe it was triggering a reflow and setting the site back to default aka: back to the top.

I set the timeout to 0 which  moves the execution to the next loop event. It gives the browser time to do one and then the other, not both at the same time.

I moved `onKeyPress` to `onKeyDown` due to the former being depreciated.
2024-09-10 14:52:12 +10:00
Gykes
a44993bbf4 Fix source-selector being blocked on mobile (#5229)
Small CSS change to allow the `source-selector` to be brought to the front of the controls to allow people to select which source they would like.
2024-09-10 14:43:09 +10:00
DogmaDragon
ba83da1983 Add note about saved filter sorting (#5234) 2024-09-08 01:20:44 +03:00
WithoutPants
0a98296642 Fix scroll to top behaviour (#5228) 2024-09-06 13:53:23 +10:00
WithoutPants
ca970b9706 Use gallery updated at for cover mod time (#5225) 2024-09-05 16:45:15 +10:00
WithoutPants
2b288fd67c Add changelog for 0.27 (#5224) 2024-09-05 16:35:14 +10:00
WithoutPants
7f1ad30db1 Show option for sub-content only if there are child objects (#5223) 2024-09-05 16:34:56 +10:00
yoshnopa
5721ea2b70 Gallery scrubber wall view (#5191)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-09-05 13:06:43 +10:00
WithoutPants
8c2a25b833 Fix gallery scrubber image order (#5222) 2024-09-05 12:59:20 +10:00
DirtyRacer1337
601a16b5cb replace stashBox validation (#5187) 2024-09-05 12:59:05 +10:00
dogwithakeyboard
879c20efc7 Add linkTypes to performer popover (#5195) 2024-09-05 12:55:19 +10:00
yoshnopa
283f76240f Make Scrubbers touchscreen capable (#5183) 2024-09-05 11:43:31 +10:00
WithoutPants
ad17e7defe Fix handling of files to delete during delete Gallery operation (#5213)
* Only remove file in zip from image if deleting from zip file
* Only remove file in folder from image if deleting from folder
2024-09-05 11:27:31 +10:00
WithoutPants
7a2e59fcef Fix scene filter panel colour slider range (#5221)
* Refactor SceneVideoFilterPanel sliders.

Fix colour values to go between 0-200%.

* Add cursor for filter slider values to hint interaction
2024-09-05 11:26:25 +10:00
WithoutPants
7c09f24f34 Don't try to migrate non-existent vtt files (#5216) 2024-09-05 11:25:56 +10:00
WithoutPants
fb82866512 Don't show move drop target on non-move drag operations (#5219) 2024-09-05 11:25:30 +10:00
Gykes
15da2c1f4c Fix select field alias odd spacing (#5218)
* Fix Tag and Alias odd spacing

As Echo6ix brough up the HTML Engine doesn't generate whitespace at the beginning of a string. Modifying it to use `&nbsp;` so that the spacing will be correct.

fixes https://github.com/stashapp/stash/issues/4997

* update for performerSelect and studioSelect
2024-09-05 11:25:05 +10:00
WithoutPants
1dac598755 Remove console.log. Remove vestigial property (#5217) 2024-09-05 09:35:56 +10:00
Ian McKenzie
ad442fbee5 lint: switch exportloopref to copyloopvar per warning message (#5212) 2024-09-04 16:05:44 +10:00
WithoutPants
4e9925fd3f Show page numbers on low page count (#5206)
Shows individual page numbers instead of the page count selector when pages < 4.
2024-09-04 09:41:53 +10:00
WithoutPants
7b064ac99e Only give height to gallery-container on larger devices (#5205)
Having height/overflow on the stacked/vertical orientation causes weird scrolling behaviour.
2024-09-04 09:39:59 +10:00
WithoutPants
a8a3b4cfd9 Don't focus query field on select (#5204) 2024-09-04 09:39:41 +10:00
WithoutPants
306ba63ab6 Prevent window scrolling on mouse wheel scroll when numeric input field is focused and hovered. (#5199) 2024-09-03 16:33:49 +10:00
WithoutPants
c21ded028a Scan video orientation (#5189)
* Adjust video dimensions for side data rotation
* Warn user when ffprobe version < 5. Only get rotation data on version >= 5
2024-09-03 16:33:15 +10:00
WithoutPants
899ee713ab Adjust image dimensions for exif orientation (#5188) 2024-09-03 16:32:29 +10:00
WithoutPants
a3c34a51aa Gallery cover url (#5182)
* Add default gallery image
* Add gallery cover URL path
* Use new cover URL in UI
* Hide gallery preview scrubber when gallery has no images
* Don't try to show lightbox for gallery without images
* Ignore unrelated lint issue
2024-09-03 16:31:55 +10:00
DogmaDragon
010a355e0b Add info about regex modifiers (#5200) 2024-09-02 22:44:17 +03:00
WithoutPants
bcf0fda7ac Containing Group/Sub-Group relationships (#5105)
* Add UI support for setting containing groups
* Show containing groups in group details panel
* Move tag hierarchical filter code into separate type
* Add depth to scene_count and add sub_group_count
* Add sub-groups tab to groups page
* Add containing groups to edit groups dialog
* Show containing group description in sub-group view
* Show group scene number in group scenes view
* Add ability to drag move grid cards
* Add sub group order option
* Add reorder sub-groups interface
* Separate page size selector component
* Add interfaces to add and remove sub-groups to a group
* Separate MultiSet components
* Allow setting description while setting containing groups
2024-08-30 11:43:44 +10:00
Ian McKenzie
96fdd94a01 Create a section in the history panel to reset scene activity (#5168)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-08-29 13:34:22 +10:00
sezzim
68738bd227 Support for assigning any image from a gallery as the cover (#5053)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-08-29 11:24:52 +10:00
dependabot[bot]
8133aa8c91 Bump micromatch from 4.0.5 to 4.0.8 in /ui/v2.5 (#5180)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 10:21:04 +10:00
Andi
ae1841efb0 chore: use errors.New to replace fmt.Errorf with no parameters will much better (#4778) 2024-08-28 14:45:57 +10:00
WithoutPants
27aef4ac2e Update gqlgen and gqlparser dependencies (#5179) 2024-08-28 14:31:56 +10:00
Gykes
b3d6a8eedd Removing Play Button With No File (#5141)
* Remove Play Button With No File
* Hide controls when there is no file
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-08-28 10:50:04 +10:00
Gykes
a023a86ca6 Fix Studio Pluralization (#5161)
Small bug fix so that if a studio only has 1 child studio then the correct pluralization is used.
2024-08-28 10:20:16 +10:00
WithoutPants
294e2090d0 Scene player presentation improvements (#5145)
* Show controls before video plays
* Allow interaction with controls while displaying error
* Source selector improvements

Don't auto-play next source if manually selected.
Don't remove errored sources

* Show errored sources in different style
2024-08-28 10:10:47 +10:00
Gykes
c69d72b243 Add Overlay-duration to span (#5177)
Adding overlay-duration so it has it's own dedicated class. Helps with theming/customization

closes https://github.com/stashapp/stash/issues/4240
2024-08-28 09:40:58 +10:00
Gykes
cdea9374d8 Standardizing the delete dropdown. (#5176)
Changed to use "delete" rather than "delete_entity"
2024-08-28 09:23:58 +10:00
WithoutPants
b1b223c90a Persist tagger settings and change defaults (#5165)
* Persist tagger settings in UIConfig
* Show males and set tags by default
* Add release note
2024-08-28 09:19:50 +10:00
WithoutPants
c74456c07e Bump linux ffmpeg URL for latest version (#5172) 2024-08-28 09:07:49 +10:00
WithoutPants
ca55f96fd8 Replace group image with more consistent svg (#5170) 2024-08-28 09:02:52 +10:00
WithoutPants
b7799df2a6 Add package docs and project vision/goals (#5169)
* Add goals/design vision to contributing doc
* Add barebones package documentation
2024-08-28 09:01:39 +10:00
WithoutPants
10341fba58 Update builtin freeones scraper (#5171) 2024-08-28 09:00:14 +10:00
WithoutPants
996dfb1c2f Gallery scrubber (#5133) 2024-08-28 08:59:41 +10:00
NodudeWasTaken
ce47efc415 Add video codec profiles (#5154) 2024-08-27 18:03:48 +10:00
dogwithakeyboard
3089e1ad69 Markers scene filter (#5097)
* Add scene filter to markers
* Fix labels for scenes
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-08-27 17:23:34 +10:00
Ian McKenzie
62ff6f3c7f Use existing consts for table names in anonymise.go where available (#5167) 2024-08-27 13:01:12 +10:00
Ian McKenzie
e49beb139c Truncate scenes_o_dates and scenes_view_dates as part of anonymize (#5166) 2024-08-27 09:03:22 +10:00
DogmaDragon
d8ee57cd50 [Docs] add note about caption functionality [skip ci] 2024-08-23 02:31:33 +03:00
Gykes
427c18be7d QOL Move Refresh Scrapers to Top (#5142)
QOL change to move the "Refresh Scrapers" button within the "Scrape with..." dropdown to the top.
2024-08-21 09:22:59 +10:00
Gykes
7788a6fd07 PatchComponentRedo (#5136)
* PatchComponent update specifically for SettingsInterfacePanel
* Fix unrelated lint issues
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-08-20 14:25:06 +10:00
WithoutPants
49060e6686 UI nested instead (#5125)
* Support multiple calls to PluginApi.patch.instead for a component.

Allow calling the original/chained function from the hook function.

* Add example of new usage of instead
* Update documentation
2024-08-20 12:36:45 +10:00
WithoutPants
a94bf29b34 Add missing performer sort options to whitelist (#5129)
Adds career length, measurements and weight.
2024-08-13 16:13:15 +10:00
hwill83
ecb53cee55 Fix broken link in development documentation. (#5128)
The existing link 404s.
2024-08-13 09:08:04 +10:00
WithoutPants
fb77e18182 Fix view history imported from o-history json (#5127)
* Fix view history imported from o-history json
* Add scene import unit tests
2024-08-13 09:07:36 +10:00
WithoutPants
c47aafff66 Filter issue fixes (#5126)
* Fix filter reading from URL when not active
* Use alternative clone mechanism. Fixes weird filter hook behaviour
* Separate search term input component
2024-08-12 14:10:10 +10:00
WithoutPants
aa1894964f Codeberg weblate update (#5123)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/

* Translated using Weblate (French)

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/es/

* Translated using Weblate (Estonian)

Currently translated at 95.7% (1106 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/et/

* Translated using Weblate (French)

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Translated using Weblate (German)

Currently translated at 85.7% (990 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (German)

Currently translated at 86.8% (1003 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.0% (855 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/pt_BR/

* Translated using Weblate (Finnish)

Currently translated at 75.7% (875 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/fi/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 0.4% (5 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nb_NO/

* Added translation using Weblate (Hindi)

* Added translation using Weblate (Latvian)

* Translated using Weblate (Hindi)

Currently translated at 5.7% (66 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/hi/

* Translated using Weblate (Latvian)

Currently translated at 5.6% (65 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/lv/

* Translated using Weblate (German)

Currently translated at 87.9% (1016 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/de/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/zh_Hans/

* Added translation using Weblate (Norwegian Nynorsk)

* Translated using Weblate (Norwegian Nynorsk)

Currently translated at 10.3% (120 of 1155 strings)

Translation: stash/stash
Translate-URL: https://translate.codeberg.org/projects/stash/stash/nn/

---------

Co-authored-by: Codeberg Translate <translate@codeberg.org>
Co-authored-by: Larsluph <Larsluph@users.noreply.translate.codeberg.org>
Co-authored-by: gallegonovato <gallegonovato@users.noreply.translate.codeberg.org>
Co-authored-by: Zesty6249 <Zesty6249@users.noreply.translate.codeberg.org>
Co-authored-by: doodoo <doodoo@users.noreply.translate.codeberg.org>
Co-authored-by: AlpacaSerious <AlpacaSerious@users.noreply.translate.codeberg.org>
Co-authored-by: wql219 <wql219@users.noreply.translate.codeberg.org>
Co-authored-by: BSSPM <BSSPM@users.noreply.translate.codeberg.org>
Co-authored-by: icaro <icaro@users.noreply.translate.codeberg.org>
Co-authored-by: IiroS <IiroS@users.noreply.translate.codeberg.org>
Co-authored-by: DogmaDragon <DogmaDragon@users.noreply.translate.codeberg.org>
Co-authored-by: saumya <saumya@users.noreply.translate.codeberg.org>
Co-authored-by: Marky05 <Marky05@users.noreply.translate.codeberg.org>
Co-authored-by: human-corset <human-corset@users.noreply.translate.codeberg.org>
Co-authored-by: tzuuuss <tzuuuss@users.noreply.translate.codeberg.org>
Co-authored-by: throbbing <throbbing@users.noreply.translate.codeberg.org>
2024-08-12 09:29:34 +10:00
blackx69
c8d4dacffd Interactive Tools Enhancements Support (#5115)
* added `useInteractive` hook and exposed to `PluginApi`
* made `SceneFileInfoPanel` patchable
2024-08-06 10:34:27 +10:00
WithoutPants
c79f299d1a Add clone methods to all criterion classes (#5109) 2024-08-02 18:32:11 +10:00
WithoutPants
6a5dc4e774 Refactor ItemList code and re-enable viewing sub-tag/studio content (#5080)
* Refactor list filter to use contexts
* Refactor FilteredListToolbar
* Move components into separate files
* Convert ItemList hook into components
* Fix criteria clone functions
* Add toggle for sub-studio content
* Add toggle for sub-tag content
* Make LoadingIndicator height smaller and fade in.
2024-07-31 16:35:37 +10:00
WithoutPants
540d72bc44 Fix bulk scene setting groups (#5106) 2024-07-31 10:53:40 +10:00
WithoutPants
d96850c008 Rename movie tables to groups in database schema (#5082)
* Rename movie tables to groups
* Correct index name
* Rename synopsis to description in schema
2024-07-30 14:14:16 +10:00
thundxrr
48c6373afa Added detection for n-prefixed ffmpeg version string (#5102) 2024-07-29 21:54:04 +10:00
CJ
5512d37da3 fix missing transgender color icon (#5090) 2024-07-29 14:38:37 +10:00
Lenny3D
f79677ba96 Copy apikey query parameter to DASH & HLS manifest (#5061)
* Copy apikey query parameter to DASH & HLS manifest

When an API key is provided to the DASH and HLS manifest endpoints, this
it will now be copied to the URLs inside the manifest. This allows for
clients that are only able to pass an URL to an (external) video player
to function in case authentication is set up on stash.
2024-07-16 13:17:18 +10:00
dogwithakeyboard
bfd8e81ffd add birthdate to performer select and restyle (#5076) 2024-07-16 13:16:57 +10:00
WithoutPants
720b233be6 Rename movie group backend (#5044)
* Rename movie go files
* Rename movie package to group
2024-07-04 11:36:05 +10:00
WithoutPants
3ddfafa831 Fix background image for group and studio 2024-07-04 11:35:35 +10:00
WithoutPants
f598fa71da Use the rescan option when rescanning files from menu (#5043) 2024-07-04 11:24:03 +10:00
WithoutPants
6cebf146cb Fix validate-ui-quick to only run required checks 2024-07-04 11:11:26 +10:00
WithoutPants
d0caf87eeb Add quick fmt/validate targets 2024-07-04 10:53:29 +10:00
WithoutPants
a0b082a36d Various detail page refactoring (#5037)
* Refactor repeated code into BackgroundImage
* Move BackgroundImage into Details folder
* Refactor performer tabs
* Refactor studio tabs
* Refactor tag tabs
* Refactor repeated code into DetailTitle
* Refactor repeated collapse button code into component
* Reuse FavoriteIcon in details pages
* Refactor performer urls into component
* Refactor alias list into component
* Refactor repeated image code into HeaderImage and LightboxLink components
* Replace render functions with inline conditional rendering
* Support new twitter hostname
2024-07-04 10:52:46 +10:00
WithoutPants
ec23b26c60 Adds mutex protection around dms.Eventing (#5042)
It's possible for concurrent map read/write panic in the Eventing.Subscribe function.
2024-07-04 10:52:04 +10:00
WithoutPants
15a7b8a859 Movie group renames (#5039)
* Rename Movie and MoviePartial to Group/GroupPartial
* Rename Movie interfaces
* Update movie url builders to use group
* Rename movieRoutes to groupRoutes
* Update dataloader
* Update names in sqlite package
* Rename in resolvers
* Add GroupByURL to scraper config
* Scraper backward compatibility hacks
2024-07-04 09:10:26 +10:00
WithoutPants
b69d9cc840 Metadata Providers -> Scraper list improvements (#5040)
* Refactor scraping settings panel
* Add max-height to scraper table
* Separate scraper section
* Add filter to scrapers section
* Add counters to scraper headings
* Show all urls with a scrollbar
* Sort URLs
2024-07-04 09:09:31 +10:00
WithoutPants
12917f51d0 Scraper menu filter (#5041)
* Move scene scraper menu into reusable component
* Reuse ScraperMenu for scene query menu
* Reuse scraper menu in GalleryEditPanel
* Add filter to scraper menu
* Add divider between stashboxes and scrapers
2024-07-04 09:01:35 +10:00
WithoutPants
a3e72b61ee Rename movie components to group (#5038) 2024-07-03 14:17:02 +10:00
WithoutPants
2739696813 Add group graphql interfaces (#5017)
* Deprecate movie and add group interfaces
* UI changes
2024-07-03 13:59:40 +10:00
DogmaDragon
f477b996b5 Update README.md [skip ci]
Fix typo
2024-07-01 17:35:46 +03:00
DogmaDragon
70250c93f1 Update translation instance (#5031)
Replace (incomplete) flag names with SVG banner.
2024-07-01 16:15:21 +10:00
WithoutPants
4cca3b298d Add Opus as supported audio for mp4 (#5030) 2024-07-01 11:19:38 +10:00
dependabot[bot]
436ae0a027 Bump ws from 8.16.0 to 8.17.1 in /ui/v2.5 (#4980)
Bumps [ws](https://github.com/websockets/ws) from 8.16.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.16.0...8.17.1)

---
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>
2024-07-01 11:18:54 +10:00
barraged1
dc3ce2b414 updating scrapedPerformerToCreateInput to pass Disambiguation (#5029) 2024-07-01 11:18:20 +10:00
dependabot[bot]
4244bd0b18 Bump golang.org/x/image from 0.16.0 to 0.18.0 (#5021)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.16.0 to 0.18.0.
- [Commits](https://github.com/golang/image/compare/v0.16.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 11:09:58 +10:00
WithoutPants
cbd273a19c Merge branch 'v0.26.2-stage' into develop 2024-06-27 10:24:25 +10:00
WithoutPants
2a373a25ca Update changelog 2024-06-27 10:16:42 +10:00
dogwithakeyboard
e116775d60 Check for null disambiguation on validate (#5019) 2024-06-27 10:14:14 +10:00
dogwithakeyboard
b7f938531b Check for null disambiguation on validate (#5019) 2024-06-27 10:12:39 +10:00
CJ
c5bafeb15c Address resize loop (#5004) 2024-06-27 09:11:00 +10:00
WithoutPants
205b24499b Fix key for tagger scenes (#5000) 2024-06-27 09:09:33 +10:00
WithoutPants
48035061ec Fix identify clearing parent studio when merging (#4993)
* Refactor ScrapedStudio.ToPartial signature
* Add unit test
* Don't clear parent studio during ToPartial
2024-06-27 09:08:26 +10:00
WithoutPants
af6841be49 Rename Movie to Group in UI (#4963)
* Replace movies with groups in the UI
* Massage menu items
* Change view names
* Rename Movie components to Group
* Refactor movie to group variable names
* Rename movie class names to group
2024-06-26 11:39:31 +10:00
CJ
d986a9eb4f Address resize loop (#5004) 2024-06-24 16:03:29 +10:00
WithoutPants
3156191b83 Fix scene marker query (#5014) 2024-06-24 16:02:46 +10:00
WithoutPants
593207866f Adjust 64 post-migrate where logic
I think not including the scene_id meant that a date could be corrected earlier, meaning the rows affected would be 0. Adding scene_id means that each row should be migrated one by one.
2024-06-24 16:02:18 +10:00
CJ
1f5377da1c Added path column to tables in list view (#5005) 2024-06-24 13:39:32 +10:00
NodudeWasTaken
a4e25f32ea Add apple encoder and fix extra_hw_frames bug (#4986)
* Fixes format in full hw encoding to nv12 for cuda, vaapi and qsv now
* Remove extra_hw_frames
* Add apple transcoder support
* Up the duration to discover decoding errors
* yuv420p is not supported on intel
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-06-24 13:33:27 +10:00
WithoutPants
6775a28ec7 Add schema migration to fix view_date format (#4992)
Also adds index on scene_id and adds a not null constraint to scene_id
2024-06-24 13:15:54 +10:00
WithoutPants
a8fca47a8c Fix save default filter not clearing criteria (#4999) 2024-06-21 16:16:16 +10:00
WithoutPants
2b1a57c6d0 Fix key for tagger scenes (#5000) 2024-06-21 16:15:59 +10:00
Weblate (bot)
a7e5ccd080 Translations update from Hosted Weblate (#4930)
* Translated using Weblate (Thai)

Currently translated at 77.1% (887 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/th/

* Translated using Weblate (Korean)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ko/

* Translated using Weblate (Thai)

Currently translated at 85.6% (984 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/th/

* Translated using Weblate (Thai)

Currently translated at 99.0% (1138 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/th/

* Translated using Weblate (Russian)

Currently translated at 99.9% (1148 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ru/

* Translated using Weblate (Czech)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1152 of 1152 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1153 of 1153 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1155 of 1155 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

---------

Co-authored-by: PZKL48 <nicorobinhood321@gmail.com>
Co-authored-by: 이예찬 <yechan24680@gmail.com>
Co-authored-by: Alexusfree (alexusfree) <alexusfree@gmail.com>
Co-authored-by: Nymeria <Tractorb@seznam.cz>
Co-authored-by: wql219 <160428035+wql219@users.noreply.github.com>
Co-authored-by: Hansi <hansi-go@163.com>
2024-06-19 16:07:09 -04:00
WithoutPants
a1fc14f8c4 Fix join function for studio scenes_filter handler (#4994) 2024-06-19 20:00:30 +10:00
WithoutPants
9c13b39f99 Fix identify clearing parent studio when merging (#4993)
* Refactor ScrapedStudio.ToPartial signature
* Add unit test
* Don't clear parent studio during ToPartial
2024-06-19 19:52:33 +10:00
bob123491234
b3d35dfae4 Add tags to studios (#4858)
* Fix makeTagFilter mode

* Remove studio_tags filter criterion

This is handled by studios_filter. The support for this still needs to be added in the UI, so I have removed the criterion options in the short-term.
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-06-18 15:55:20 +10:00
WithoutPants
f26766033e Performer urls (#4958)
* Populate URLs from legacy fields
* Return nil properly in xpath/json scrapers
* Improve migration logging
2024-06-18 13:41:05 +10:00
WithoutPants
fda4776d30 Movie/Group tags (#4969)
* Combine common tag control code into hook
* Combine common scraped tag row code into hook
2024-06-18 11:24:15 +10:00
WithoutPants
f9a624b803 Default view filters (#4962)
* Merge/adapt from yoshnopa:defaultDetails
* Deprecate and remove default filter calls
* Fix weird behaviour when clicking set as default
* Update deprecated get/set default filter resolvers
* Add config migration
---------
Co-authored-by: yoshnopa <usingusenet@protonmail.com>
2024-06-18 10:51:52 +10:00
well
4be60310c3 In performer scrapers, forward non-http single performer images (#4947)
* Forward non-http single performer images
* Don't set if Images already set
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-06-11 13:41:43 +10:00
WithoutPants
2d483f2d11 Bulk edit tags (#4925)
* Refactor tag relationships and add bulk edit
* Add bulk edit tags dialog
2024-06-11 13:41:20 +10:00
Maista
e18c050fb1 Add log and utils modules to the Javascript VM used in scrapers (#4937) 2024-06-11 13:21:39 +10:00
dependabot[bot]
da4d49d940 Bump braces from 3.0.2 to 3.0.3 in /ui/v2.5 (#4955)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-11 13:19:35 +10:00
WithoutPants
845d718c67 Plugin api improvements (#4935)
* Support hook into App component
* Add hookable PluginSettings component
* Add useSettings to plugin hooks
* Make setting inputs hookable
* Add hooks for performer details panel
* Update docs
2024-06-11 13:18:45 +10:00
WithoutPants
ed057c971f Correct Stash box endpoint inputs (#4924)
* Use stashbox endpoint instead of index
* Update UI to not use deprecated fields
2024-06-11 13:14:12 +10:00
WithoutPants
94a978d063 Scraper inputs (#4922)
* Pass more details in scene/gallery scrape
2024-06-11 13:12:45 +10:00
Flashy78
dcb86d9186 Allow SSL cert paths to be specified in config (#4910) 2024-06-11 13:11:41 +10:00
WithoutPants
62bdff351d Movie URLs (#4900)
* Fix exclude behaviour for stringListCriterionHandlerBuilder
2024-06-11 13:08:49 +10:00
WithoutPants
bf25759a57 Validate custom locale and javascript strings (#4893)
* Validate locale json string
* Validate custom javascript string
2024-06-11 11:36:24 +10:00
WithoutPants
621e890a48 Make pagination more compact (#4882)
* Make pagination more compact

Support entering page number or clicking from drop down

* Fix border radius in dropdown in btn group
* Separate page count control
2024-06-11 11:35:28 +10:00
WithoutPants
e843c890fb Add related object filter criteria to various filter types in graphql schema (#4861)
* Move filter criterion handlers into separate file
* Add related filters for image filter
* Add related filters for scene filter
* Add related filters to gallery filter
* Add related filters to movie filter
* Add related filters to performer filter
* Add related filters to studio filter
* Add related filters to tag filter
* Add scene filter to scene marker filter
2024-06-11 11:34:38 +10:00
its-josh4
ff23d4e20b Update to Go 1.22 (#4822)
* Update to Go 1.22

Updates to Go 1.22 because 1.19 is un-supported and has some CVEs.

Also updates a small number of low-risk deps

* Explicitly install Go in CI
* Bump compiler version
* Add build tags to it target
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-06-11 10:55:02 +10:00
anonymous-ants
e4b89064b1 Fix typos in docs (en) (#4946) 2024-06-11 08:26:56 +10:00
InfiniteStash
efede32dd7 Fix studio selection in scraping dialogs (#4953) 2024-06-11 08:26:03 +10:00
WithoutPants
d1998cb5b0 Update changelog 2024-06-07 14:54:37 +10:00
WithoutPants
60446af145 Add console javascript object for backwards compatibility (#4944) 2024-06-07 14:53:51 +10:00
WithoutPants
dbfa450ace Fix tag display issue in performer scrape dialog (#4943) 2024-06-07 14:42:48 +10:00
DogmaDragon
4b8af18fab Update manual documentation (#4921) 2024-06-06 14:46:28 +10:00
NodudeWasTaken
124ea609fe Fix hw transcoding not detecting filtering errors (#4934) 2024-06-06 11:58:19 +10:00
WithoutPants
0a07194110 Fix reading task defaults (#4931) 2024-06-05 16:04:14 +10:00
WithoutPants
b232e58b06 Set config file when provided (#4909) 2024-06-03 12:44:15 +10:00
Weblate (bot)
b3f8839ef7 Translations update from Hosted Weblate (#4904)
* Translated using Weblate (French)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Thai)

Currently translated at 45.6% (525 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/th/

* Translated using Weblate (Catalan)

Currently translated at 38.7% (445 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ca/

* Translated using Weblate (French)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (French)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

---------

Co-authored-by: Larsluph <remarso59+weblate@gmail.com>
Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: PZKL48 <nicorobinhood321@gmail.com>
Co-authored-by: hardwa ps es que Retr0 <west0yss@gmail.com>
Co-authored-by: doodoo <adr.web@hotmail.fr>
2024-06-03 11:51:55 +10:00
WithoutPants
540e80c86b Support patching select sorting function (#4903)
* Fix return types for RegisterComponent and PatchFunction
* Add support for patching TagSelect.sort
* Add support for patching PerformerSelect.sort
* Patch other select component sort functions
* Document patchable functions/components
2024-05-31 18:16:31 +10:00
WithoutPants
eec31723bd Tweak relevant sort algorithm (#4902)
* Remove multi-space before getting words
* Trim names and aliases
2024-05-31 17:50:05 +10:00
WithoutPants
3b146588c6 Fix ffmpeg resolution when in current directory (#4899)
* Use absolute path to resolve ffmpeg in config directory
* Pass absolute config path to plugins
2024-05-30 15:50:27 +10:00
WithoutPants
2b699fcf95 Default create missing to true in Identify (#4873)
* Default create missing to true in Identify
* Update manual
2024-05-30 13:12:07 +10:00
Weblate (bot)
d6158d70a9 Translations update from Hosted Weblate (#4878)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Added translation using Weblate (Afrikaans)

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Afrikaans)

Currently translated at 3.6% (42 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/af/

* Add missing language options and rename locale files

---------

Co-authored-by: wql219 <160428035+wql219@users.noreply.github.com>
Co-authored-by: ceresbeet <ceresbeet@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-05-30 10:26:22 +10:00
WithoutPants
cf45ac883e Fix operations not using effective filter on sub-view pages (#4896) 2024-05-29 08:01:54 +10:00
WithoutPants
e4267a0d83 Fix relevance sorting when name/alias contains leading spaces (#4895) 2024-05-29 07:49:23 +10:00
WithoutPants
2ca53714a6 Fix SQL error when querying performers with missing aliases (#4894) 2024-05-29 07:48:35 +10:00
DogmaDragon
0ff0f9c8ec Delete .github/FUNDING.yml (#4887)
Replaced with org-wide funding.yml file. https://github.com/stashapp/.github/blob/main/FUNDING.yml
2024-05-28 16:38:30 +10:00
WithoutPants
9c8bd853c5 Fix lint error 2024-05-28 16:37:13 +10:00
WithoutPants
bf0e0f2210 Add v0.26.0 changelog (#4875) 2024-05-24 12:13:30 +10:00
WithoutPants
c314515b8f Add polyfill for EventTarget constructor (#4874) 2024-05-24 08:06:41 +10:00
WithoutPants
28b5fbfd4d Apply scraped tag exclusions galleries and performers (#4872) 2024-05-24 08:06:23 +10:00
WithoutPants
3dd218e1ba Clarify stash hosted funscript description #4850 2024-05-23 14:35:39 +10:00
WithoutPants
eb67f7f4d6 Fix corrupted frontPageContent keys during migration (#4870)
* Add NestedMap.Delete
* Migrate corrupt frontPageContent keys
2024-05-23 13:59:39 +10:00
WithoutPants
98d210f7f9 Fix inconsistent field names in javascript plugin hooks (#4869) 2024-05-23 11:28:15 +10:00
WithoutPants
4794a1d453 Fix setting pointers corrupting config in memory (#4868) 2024-05-23 10:56:18 +10:00
puc9
77ef16570b Add JS plugin name to the log line (#4867) 2024-05-23 08:05:12 +10:00
WithoutPants
99d97804f4 Change umask when creating config file to exclude user write (#4866) 2024-05-22 14:59:25 +10:00
WithoutPants
89553864f5 Enforce whitelist for sort values (#4865) 2024-05-22 14:59:08 +10:00
WithoutPants
865208844c Fix python not being resolved correctly if not in path (#4864)
* Don't replace plugin exec path if python command. Don't clobber exec
* Fix logging of python resolve errors
2024-05-22 14:57:36 +10:00
Weblate (bot)
062d566195 Translations update from Hosted Weblate (#4694)
* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1136 of 1136 strings)

Co-authored-by: Dee <dongfengweixiao@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/
Translation: stashapp/stash

* Translated using Weblate (Bengali (Bangladesh))

Currently translated at 22.7% (259 of 1139 strings)

Co-authored-by: Faridin Tzy <faridin05saif@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/bn_BD/
Translation: stashapp/stash

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (1146 of 1146 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (1146 of 1146 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (1139 of 1139 strings)

Translated using Weblate (Swedish)

Currently translated at 99.5% (1134 of 1139 strings)

Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/
Translation: stashapp/stash

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1146 of 1146 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1146 of 1146 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1139 of 1139 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1139 of 1139 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1139 of 1139 strings)

Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/
Translation: stashapp/stash

* Translated using Weblate (Czech)

Currently translated at 100.0% (1146 of 1146 strings)

Translated using Weblate (Czech)

Currently translated at 89.7% (1022 of 1139 strings)

Co-authored-by: Nymeria <Tractorb@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/
Translation: stashapp/stash

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/
Translation: stashapp/stash

* Translated using Weblate (Korean)

Currently translated at 82.2% (943 of 1146 strings)

Co-authored-by: キムキム厶 <kimukimusi52@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ko/
Translation: stashapp/stash

* Translated using Weblate (Russian)

Currently translated at 100.0% (1147 of 1147 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (1146 of 1146 strings)

Co-authored-by: Old gnome <orpgnome@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ru/
Translation: stashapp/stash

* Translated using Weblate (Italian)

Currently translated at 79.0% (906 of 1146 strings)

Co-authored-by: Walter Saporiti <monsena@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/it/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 78.7% (902 of 1146 strings)

Co-authored-by: Furin Kazan <nezoko@digdig.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 78.7% (902 of 1146 strings)

Co-authored-by: すずひろ <suzuhiroruri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 78.9% (905 of 1146 strings)

Translated using Weblate (Japanese)

Currently translated at 78.7% (902 of 1146 strings)

Co-authored-by: Furin Kazan <nezoko@digdig.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 83.7% (960 of 1146 strings)

Co-authored-by: すずひろ <suzuhiroruri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 83.7% (960 of 1146 strings)

Co-authored-by: Furin Kazan <nezoko@digdig.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Spanish)

Currently translated at 74.0% (849 of 1146 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 84.5% (969 of 1146 strings)

Co-authored-by: すずひろ <suzuhiroruri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Japanese)

Currently translated at 84.5% (969 of 1146 strings)

Co-authored-by: Furin Kazan <nezoko@digdig.org>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ja/
Translation: stashapp/stash

* Translated using Weblate (Korean)

Currently translated at 97.9% (1122 of 1146 strings)

Translated using Weblate (Korean)

Currently translated at 97.9% (1122 of 1146 strings)

Co-authored-by: 이예찬 <yechan24680@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ko/
Translation: stashapp/stash

* Translated using Weblate (Spanish)

Currently translated at 78.2% (897 of 1146 strings)

Co-authored-by: VoloShiNov <rucholcf@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/
Translation: stashapp/stash

* Translated using Weblate (Spanish)

Currently translated at 79.0% (907 of 1147 strings)

Translated using Weblate (Spanish)

Currently translated at 78.4% (899 of 1146 strings)

Translated using Weblate (Spanish)

Currently translated at 78.2% (897 of 1146 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/
Translation: stashapp/stash

* Translated using Weblate (Indonesian)

Currently translated at 44.7% (513 of 1147 strings)

Translated using Weblate (Indonesian)

Currently translated at 43.8% (503 of 1146 strings)

Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/id/
Translation: stashapp/stash

* Translated using Weblate (German)

Currently translated at 83.9% (963 of 1147 strings)

Translated using Weblate (German)

Currently translated at 83.5% (957 of 1146 strings)

Co-authored-by: Justus Nacken <justus.nacken@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/de/
Translation: stashapp/stash

* Translated using Weblate (Finnish)

Currently translated at 73.1% (838 of 1146 strings)

Co-authored-by: gimmeliina <jarruraita@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fi/
Translation: stashapp/stash

* Translated using Weblate (German)

Currently translated at 83.9% (963 of 1147 strings)

Co-authored-by: Ben <benteske.horny+hostedwebplate@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/de/
Translation: stashapp/stash

* Translated using Weblate (Spanish)

Currently translated at 79.0% (907 of 1147 strings)

Co-authored-by: BodoBaas <rossgelle67@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/
Translation: stashapp/stash

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1147 of 1147 strings)

Co-authored-by: Philip Wang <philpw99@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/
Translation: stashapp/stash

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1147 of 1147 strings)

Co-authored-by: 张Ly <zanzhz1101@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/
Translation: stashapp/stash

* Added translation using Weblate (Nepali)

Co-authored-by: Lazy Bone <pcoc2779@gmail.com>

* Translated using Weblate (Catalan)

Currently translated at 9.4% (108 of 1147 strings)

Added translation using Weblate (Catalan)

Co-authored-by: hardwa ps es que Retr0 <west0yss@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ca/
Translation: stashapp/stash

* Translated using Weblate (Czech)

Currently translated at 100.0% (1147 of 1147 strings)

Co-authored-by: Adam Beneš <toohka@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/
Translation: stashapp/stash

* Translated using Weblate (Catalan)

Currently translated at 22.1% (254 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ca/

* Translated using Weblate (Spanish)

Currently translated at 79.0% (907 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

* Translated using Weblate (Catalan)

Currently translated at 29.2% (336 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ca/

* Translated using Weblate (Spanish)

Currently translated at 94.0% (1079 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

* Translated using Weblate (Spanish)

Currently translated at 99.9% (1146 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1147 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Italian)

Currently translated at 78.9% (906 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/it/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1147 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1147 of 1147 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (1149 of 1149 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/es/

---------

Co-authored-by: Dee <dongfengweixiao@hotmail.com>
Co-authored-by: Faridin Tzy <faridin05saif@gmail.com>
Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: Nymeria <Tractorb@seznam.cz>
Co-authored-by: キムキム厶 <kimukimusi52@gmail.com>
Co-authored-by: Old gnome <orpgnome@users.noreply.hosted.weblate.org>
Co-authored-by: Walter Saporiti <monsena@gmail.com>
Co-authored-by: Furin Kazan <nezoko@digdig.org>
Co-authored-by: すずひろ <suzuhiroruri@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: 이예찬 <yechan24680@gmail.com>
Co-authored-by: VoloShiNov <rucholcf@hotmail.com>
Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
Co-authored-by: Justus Nacken <justus.nacken@gmail.com>
Co-authored-by: gimmeliina <jarruraita@outlook.com>
Co-authored-by: Ben <benteske.horny+hostedwebplate@gmail.com>
Co-authored-by: BodoBaas <rossgelle67@gmail.com>
Co-authored-by: Philip Wang <philpw99@gmail.com>
Co-authored-by: 张Ly <zanzhz1101@gmail.com>
Co-authored-by: Lazy Bone <pcoc2779@gmail.com>
Co-authored-by: hardwa ps es que Retr0 <west0yss@gmail.com>
Co-authored-by: Adam Beneš <toohka@protonmail.com>
Co-authored-by: Faileador <faileador1@gmail.com>
Co-authored-by: wql219 <160428035+wql219@users.noreply.github.com>
Co-authored-by: parduz <parduz@yahoo.it>
2024-05-21 17:03:44 +10:00
WithoutPants
bfc60bb23f Replace viper with koanf (#4845)
* Migrate to koanf
* Use temp logger for crashes before config is initialised
* Remove snake case hacks
* Add migration for config file keys
* Add migration note for new migration
* Renamed viper functions
* Remove front-end viper workaround
* Correctly default scan options
2024-05-21 11:24:47 +10:00
CJ
0fa71be697 Add scan option to force gallery zip rescan (#4113)
* Add scan option to force rescan
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-05-20 13:52:36 +10:00
dependabot[bot]
5ba1ea8fbc Bump vite from 4.5.2 to 4.5.3 in /ui/v2.5 (#4745)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.2 to 4.5.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.3/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-20 13:44:56 +10:00
WithoutPants
4d3dc0aec8 Default SetCoverImage to true in identify (#4855) 2024-05-20 13:13:16 +10:00
CJ
b12269e477 update popover delay (#4835)
* adjust leave delay to match enter delay
2024-05-20 13:13:01 +10:00
dogwithakeyboard
e32593023e Add additional fields and restyle Movie select and Gallery select (#4851)
* Add new fields and restyle gallery selector
* Add new fields and style movie selector
2024-05-20 13:10:36 +10:00
bob123491234
3e3e8b95e2 Add scenes filter to galleries (#4840) 2024-05-20 13:04:45 +10:00
WithoutPants
769540be55 Warn if ffmpeg lacks codecs (#4852)
Prefer ffmpeg with codec support if path not explicitly set.
2024-05-20 12:54:44 +10:00
WithoutPants
1ffca39e1d Fix values being reset when changing mode (#4854) 2024-05-20 12:54:29 +10:00
Ivan Pedrazas
dd84714a16 feat: Make DLNA port configurable (#4836)
---------
Signed-off-by: Ivan Pedrazas <ipedrazas@gmail.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-05-20 09:33:23 +10:00
damontecres
ad844a225c Return 401 code for ErrUnauthorized (#4842) 2024-05-16 14:30:19 +10:00
dogwithakeyboard
ca5febc65b New scene select with additional fields (#4832) 2024-05-14 14:51:24 +10:00
NotForMyCV
c8aeb7966a Add last_played_at filter (#4829) 2024-05-14 14:40:46 +10:00
CJ
1d565a7cbd Enable track activity by default (#4710)
* enable track Activity by default
* Add v0.26.0 release notes and update "Track Activity" label
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-05-14 12:02:59 +10:00
WithoutPants
408d6fc988 Build UI artifacts (#4824)
* Flag/env var for stash UI location
* Include UI in build artifacts
2024-05-14 12:02:29 +10:00
feederbox826
237a904ca4 add stashapp-tools to default docker install (#4488)
Co-authored-by: feederbox826 <feederbox826@users.noreply.github.com>
2024-05-11 15:26:03 +03:00
WithoutPants
12af7d6515 Fix black screen after migrating with release notes (#4825) 2024-05-10 16:42:33 +10:00
WithoutPants
77ee620877 Fix ffmpeg version detection
Fixes issue where ffmpeg version could not be detected if the version number had no patch number.
2024-05-10 16:36:08 +10:00
NodudeWasTaken
c5fef3977e Full hardware transcoding (#4765) 2024-05-10 15:55:31 +10:00
Dankonite
29859fa4ad Tag Favoriting (#4728)
* Add missing key unbind
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-05-09 12:04:58 +10:00
Serge Levin
1cee1ccfe2 Better support for Samsung SmartTV for built-in DLNA server (#4784) 2024-05-09 09:16:21 +10:00
HookedBehemoth
9cc26f7b75 skip reencoding compatible video streams (#4783)
* skip reencoding compatible video streams
* don't attempt copy on transcode with resize
2024-05-08 13:24:13 +10:00
WithoutPants
c5abe28375 Fix alias issue when tagging performer from stash-box (#4820) 2024-05-08 12:47:18 +10:00
dependabot[bot]
1b99a03847 Bump golang.org/x/net from 0.17.0 to 0.23.0 (#4773)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-08 12:44:10 +10:00
WithoutPants
22d14fd89e Clean duplicate aliases when creating from performer tagger (#4801)
* Clean duplicate aliases when creating from performer tagger
* Use case insensitive name matching
2024-05-03 13:10:35 +10:00
WithoutPants
0bba8889b8 Fix duplicate scene checker select logic (#4800) 2024-05-03 13:10:17 +10:00
WithoutPants
141f60f8fb Fix interactive speed being lost when file is moved (#4799) 2024-05-03 13:10:05 +10:00
WithoutPants
560bdcd60d Fix filterHook not applied to scene card queue (#4798) 2024-05-03 13:09:42 +10:00
Emilo2
c43e7b4351 Select first result when selectedResult is not defined (#4770) 2024-05-02 12:40:43 +10:00
hidewrong
4c0d9d0a07 chore: fix struct names (#4766)
Signed-off-by: hidewrong <hidewrong@outlook.com>
2024-05-02 12:04:54 +10:00
Maista
157b2e7bae Allow movies scraped from the scene scraper dialog to include the director field (#4757) 2024-04-17 10:30:17 +10:00
WithoutPants
ec6acab2f4 Details operation toolbar (#4714)
* Add scene detail header
* Make common count button and add view count
* Add titles to play count and o count buttons
* Move rating from edit panel
* Include frame rate in header
* Remove redundant title/studio
* Improve numeric rating presentation
* Add star where there is no rating header
* Set rating on blur when click to edit
* Add star to numeric rating on gallery wall card
* Apply click to rate on movie page
* Apply click to rate to performer page
* Apply click to rate to studio page
* Fix rating number presentation on list tables
* Add data-value attributes
2024-04-17 10:29:36 +10:00
DogmaDragon
911da87264 Update plugins script language (#4762) 2024-04-16 00:07:57 +03:00
DogmaDragon
f7b87379d4 Merge pull request #4749 from Strategy3637/chore/link-scraping-readme 2024-04-07 20:58:14 +03:00
Strategy3637
ad60f0ebd6 Fix link to scraping documentation in README.md 2024-04-07 18:41:18 +02:00
Rémi Marseault
c83635c7a8 Add wrap on detail item values (#4730)
* Add wrap on detail item values
* Fix CSS rule order to match CI expectations
2024-04-02 18:11:18 +11:00
WithoutPants
034fd4407d Fix selected tagger search result being lost when creating objects (#4715)
* Wrap search result details
* Move utility functions to separate file
* Fix selected result being reset on object create
2024-03-27 10:40:44 +11:00
WithoutPants
7086109d78 Change ffmpeg handling (#4688)
* Make ffmpeg/ffprobe settable and remove auto download
* Detect when ffmpeg not present in setup
* Add download ffmpeg task
* Add download ffmpeg button in system settings
* Download ffmpeg during setup
2024-03-21 12:43:40 +11:00
NodudeWasTaken
a369613d42 bitrate filter (#4713) 2024-03-21 12:36:08 +11:00
WithoutPants
62b8ffb2b6 Apply filter hook to results filter only (#4705) 2024-03-21 12:07:51 +11:00
WithoutPants
213c2830d1 Fix unhandled error (#4700) 2024-03-19 15:08:20 +11:00
dogwithakeyboard
32770203ba Use new studio selector in movie scrape dialog (#4692) 2024-03-19 14:40:58 +11:00
WithoutPants
8c454582c7 Add support for favorite Studios (#4675)
* Backend changes
* Add favorite icon to studio cards
* Add favorite button to studio page
* Add studio favorite filtering
2024-03-14 11:17:44 +11:00
WithoutPants
e5929389b4 Make migration an asynchronous task (#4666)
* Add failed state and error to Job
* Move migration code
* Add websocket monitor
* Make migrate a job managed task
2024-03-14 11:06:23 +11:00
WithoutPants
fa172c2dfd Minor mobile fixes (#4683)
* Show card checkbox on mobile
* Don't focus query field on filter dialog open on touch devices
2024-03-14 11:04:25 +11:00
WithoutPants
9ceea952b6 Replace javascript module otto with goja (#4631)
* Move plugin javascript to own package with goja
* Use javascript package in scraper

Remove otto
2024-03-14 11:03:40 +11:00
bdbenim
49cd214c9d Make directors and photographers clickable in detail view (#4621)
* Make directors and photographers clickable
* Make director clickable on movie details page
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-03-14 10:34:24 +11:00
randemgame
3d0a8f653a Added Sort Performers by Last O At / Last Played At / Play Count and Added Filter Performers by Play Count. Changes to display O Count rather than O-Counter for better consistency. Grammar fixes for 'Interactive Speed' and 'pHash'. (#4649)
* Sort Performers by Last O / View

Added 2 New Sorts 'Last O At' and 'Last Played At' for Performers

* Filter Performers by Play Count

Was not sure whether to label this 'views' as the code does, or 'plays' but chose the latter as it gives parity across the scenes and performers filters.

* Sort Performers by Play Count

Reutilised the prior selectPerformerLastOAtSQL code that was used to filter by play count to additionally provide useful sorting options.

* Replaced O-Counter with O Count

To better match other sort and filter options like Gallery Count, Image Count, Play Count, Scene Count, Tag Count, File Count, Performer Count and Play Count, we should really use O Count rather than O-Counter for increased legibility and coherence.

* Title Case on 'Interactive speed' and correct capitalization for 'phash'

Every other filter/sort option is using Title Case other than 'Interactive speed' which stands out as incorrect. Also, fixing the correct mid-word capitalization on phash to pHash.

* Formatting

Formatted source code and Ran all tests
2024-03-14 10:32:08 +11:00
bob123491234
ae6d1a8109 Add galleries filter to scenes (#4632) 2024-03-14 10:17:57 +11:00
WithoutPants
7ac7963972 Save task options (#4620)
* Support setting nested UI values
* Accept partial for configureUI
* Send partial UI
* Save scan, generate and auto-tag options on change
* Send partials in saveUI
* Save library task options on change
2024-03-14 08:25:16 +11:00
Weblate (bot)
bf7cb78d6d Translations update from Hosted Weblate (#4671)
* Translated using Weblate (Danish)

Currently translated at 83.2% (946 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/da/

* Translated using Weblate (Danish)

Currently translated at 91.2% (1037 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/da/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 84.7% (963 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hant/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 84.7% (963 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hant/

---------

Co-authored-by: Heine Olsen <olsen10051988@gmail.com>
Co-authored-by: Still Hsu <dev@stillu.cc>
Co-authored-by: lunautumm <2823105878@qq.com>
2024-03-13 14:14:45 +11:00
WithoutPants
95d0e5dd34 Update changelog 2024-03-13 14:13:13 +11:00
WithoutPants
d995ce7ecb Downgrade astisub due to asticode/go-astisub#99 (#4682) 2024-03-13 10:14:45 +11:00
CJ
3521dc133e play monitoring improvement (#4670) 2024-03-12 13:33:28 +11:00
AdultSun
9f5b1c33f6 Update StashDB details in README.md (#4676)
* Update StashDB details in README.md

- Directs users to new guide in the StashDB docs instead of Discord
- No longer necessary to join Discord/Matrix for new users of StashDB now that invite codes are multi-use
- Updates formatting of the same "Quickstart Guide" section a little

* Expands quickstart language based on DogmaDragon's suggestions
2024-03-12 11:13:47 +11:00
InfiniteStash
c5bc106c1a Fix text color of medium fingerprint matches (#4662) 2024-03-08 14:59:17 +11:00
CJ
9735d0fad1 fix image card width on front page (#4665) 2024-03-08 14:40:00 +11:00
CJ
353d889fd5 fit cards code improvement (#4658) 2024-03-08 14:36:15 +11:00
WithoutPants
c7b2314bb1 Fix image clip webm not being cleaned (#4657) 2024-03-07 09:03:00 +11:00
WithoutPants
4614471ad9 Fix ffmpeg error when trying to scale and copy video (#4660) 2024-03-07 09:02:45 +11:00
Weblate (bot)
7733a214d3 Translations update from Hosted Weblate (#4641)
* Translated using Weblate (Finnish)

Currently translated at 73.5% (835 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fi/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1136 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1136 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Czech)

Currently translated at 82.9% (942 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/

* Translated using Weblate (Czech)

Currently translated at 90.4% (1027 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/

* Translated using Weblate (Italian)

Currently translated at 78.7% (895 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/it/

* Translated using Weblate (Russian)

Currently translated at 100.0% (1136 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/ru/

* Translated using Weblate (Indonesian)

Currently translated at 43.7% (497 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/id/

---------

Co-authored-by: gimmeliina <jarruraita@outlook.com>
Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: Nymeria <Tractorb@seznam.cz>
Co-authored-by: its-josh4 <myselftdev@gmail.com>
Co-authored-by: Old gnome <orpgnome@users.noreply.hosted.weblate.org>
Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
2024-03-06 10:20:42 +11:00
WithoutPants
cd2f0922ab Update changelog 2024-03-06 10:12:53 +11:00
dogwithakeyboard
f1f6e84aa0 Add bmp to image codec list (#4653) 2024-03-06 09:56:17 +11:00
WithoutPants
22986097c4 Fix code from #4570 2024-03-06 09:52:06 +11:00
WithoutPants
409a200ebc Fix URL prefix re-inserted when redirecting settings page (#4650) 2024-03-05 14:54:07 +11:00
WithoutPants
20ac388f77 Update changelog 2024-03-05 14:46:32 +11:00
CJ
0626a7aea1 fix lightbox display modes (#4644) 2024-03-05 13:37:39 +11:00
Flashy78
2ca9e0f43a Adding disambiguation to performer draft (#4122) 2024-02-29 12:47:20 +11:00
WithoutPants
b4823bec8a Use proxy in utils.ReadImageFromURL (#4637) 2024-02-29 11:28:30 +11:00
WithoutPants
945188a0ba Fix images with , character not rendering (#4636) 2024-02-29 11:28:11 +11:00
Weblate (bot)
b59afd2dcd Translations update from Hosted Weblate (#4634)
* Translated using Weblate (Indonesian)

Currently translated at 33.9% (386 of 1136 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/id/

* Add Indonesian locale option

---------

Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-02-29 10:18:20 +11:00
Weblate (bot)
9202787be0 Translations update from Hosted Weblate (#4615)
* Translated using Weblate (Swedish)

Currently translated at 100.0% (1135 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.4% (1117 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Indonesian)

Currently translated at 13.9% (158 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/id/

* Translated using Weblate (Polish)

Currently translated at 85.2% (968 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/pl/

* Translated using Weblate (French)

Currently translated at 95.5% (1085 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (French)

Currently translated at 95.5% (1085 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (Polish)

Currently translated at 85.3% (969 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/pl/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1135 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Czech)

Currently translated at 53.3% (606 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/

* Translated using Weblate (Czech)

Currently translated at 56.0% (636 of 1135 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/cs/

---------

Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
Co-authored-by: Mateusz <mateuszvx8.96@gmail.com>
Co-authored-by: doodoo <adr.web@hotmail.fr>
Co-authored-by: Nathan Lafrenière-Racine <nathan.lafreniere@gmail.com>
Co-authored-by: Nymeria <Tractorb@seznam.cz>
2024-02-28 11:54:37 +11:00
WithoutPants
4999e85fae Fix performer disambiguation styling in select 2024-02-28 11:21:29 +11:00
WithoutPants
2bdf0d9e62 Fix penis length being incorrectly truncated (#4630) 2024-02-28 11:08:28 +11:00
WithoutPants
2e00cb6c5a Document plugin settings 2024-02-28 11:07:31 +11:00
WithoutPants
33857122b8 Add 0.25.0 changelog 2024-02-28 10:18:59 +11:00
CJ
768f74a0b3 Fix unset gallery card width on initialization (#4612) 2024-02-28 09:10:15 +11:00
InfiniteStash
98c428ba4e Improve scene tagger prioritization (#4618) 2024-02-28 08:29:49 +11:00
WithoutPants
fcf249e5f6 Improve plugin hook cyclic detection (#4625)
* Move and rename HookTriggerEnum into separate package
* Move visited plugin hook handler code
* Allow up to ten plugin hook loops
2024-02-28 08:29:25 +11:00
WithoutPants
3a56dd98db Change handlerRequiredFilter to check for .forcegallery (#4627) 2024-02-28 08:28:29 +11:00
WithoutPants
48c287ed76 Add last o sort option (#4626) 2024-02-28 08:28:11 +11:00
NodudeWasTaken
9c6fbfc16f Add filename to image serve (#4616) 2024-02-27 16:11:40 +11:00
Raghavan
6a9175c954 Implement UI event dispatcher/listener (#4492)
* page change event
* expose event to plugin api
* Update UIPluginApi.md
* Add to example plugin
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-02-27 09:27:28 +11:00
WithoutPants
56896d7c7d Fix panic 2024-02-26 15:08:45 +11:00
Weblate (bot)
2e35221003 Translations update from Hosted Weblate (#4585)
* Translated using Weblate (Estonian)

Currently translated at 100.0% (1106 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/et/

* Translated using Weblate (Hungarian)

Currently translated at 42.7% (473 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/hu/

* Translated using Weblate (Hungarian)

Currently translated at 42.7% (473 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/hu/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1106 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 87.2% (965 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 80.9% (895 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hant/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 91.4% (1011 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 91.4% (1011 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 91.4% (1011 of 1106 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.6% (1071 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.6% (1071 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.6% (1071 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.7% (1072 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.7% (1072 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.8% (1073 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.8% (1073 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 96.8% (1073 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Added translation using Weblate (Indonesian)

* Translated using Weblate (Swedish)

Currently translated at 100.0% (1108 of 1108 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (1098 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (1098 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (1098 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (1098 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Indonesian)

Currently translated at 8.3% (94 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/id/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 97.6% (1098 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.0% (1103 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.4% (1107 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.4% (1107 of 1125 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/zh_Hans/

---------

Co-authored-by: LauriL <TheLauri@users.noreply.hosted.weblate.org>
Co-authored-by: Kolbász <services@perjel.hu>
Co-authored-by: R Balu <balu.ka@hotmail.com>
Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: BugGeeker <darling940620@foxmail.com>
Co-authored-by: Y. Chen <daniel.yifu.chen@gmail.com>
Co-authored-by: 宿命 <331874545@qq.com>
Co-authored-by: 张Ly <zanzhz1101@gmail.com>
Co-authored-by: wql219 <wanqinglin219@hotmail.com>
Co-authored-by: Tukimin Satrio <k797du3eh@mozmail.com>
Co-authored-by: celery1806 <qincai233@gmail.com>
2024-02-23 16:36:12 +11:00
WithoutPants
ba1ebba6c0 Add Clean generated files task (#4607)
* Add clean generate task
* Add to library tasks
* Save and read defaults
* Stop handling and logging
* Make filename parsing more robust
2024-02-23 15:56:00 +11:00
WithoutPants
4a3ce8b6ec Fix auto tag from object not honouring the ignore autotag flag (#4610)
* Fix auto tag from object ignoring the ignore autotag field
* Disable auto tag buttons where ignore auto tag is enabled
2024-02-23 14:34:04 +11:00
WithoutPants
4b84ec0d85 Support setting galleries in multiple images (#4608) 2024-02-23 11:42:04 +11:00
WithoutPants
a302fc78ea Fix crash when generating thumbnail during scan 2024-02-23 11:20:07 +11:00
WithoutPants
f2bc3d5567 Log on scene cover fetch error 2024-02-22 14:59:03 +11:00
WithoutPants
a303446bb7 Scene play and o-counter history view and editing (#4532)
Co-authored-by: randemgame <61895715+randemgame@users.noreply.github.com>
2024-02-22 11:28:18 +11:00
WithoutPants
0c2a2190e5 Plugin API improvements (#4603)
* Accept plain map for runPluginTask
* Support running plugin task without task name
* Add interface to run plugin operations
* Update RunPluginTask client mutation
2024-02-22 11:20:21 +11:00
WithoutPants
a8c909e0c9 Add option to generate image thumbnails during generate (#4602)
* Add option to generate image thumbnails
* Limit number of concurrent image thumbnail generation ops
2024-02-22 11:19:23 +11:00
dogwithakeyboard
c4a91d15a6 include primary tag name in search and sort (#4606) 2024-02-22 11:18:29 +11:00
WithoutPants
61bd9233b2 Fix incorrect documentation in Plugins.md 2024-02-22 08:15:40 +11:00
dogwithakeyboard
37acd6b79b restore image performer age filter (#4601) 2024-02-21 11:22:04 +11:00
WithoutPants
5bb9bf902c Show upgradable packages only when checking for updates (#4599)
* Sort upgradable packages to top
* Show upgradable packages only by default
* Fix loading state when refetching
2024-02-21 08:24:18 +11:00
WithoutPants
76e5598876 Improve handling of moved and added video files (#4598)
* If old file path is not in library, treat as move
* Use existing phash if file with same oshash exists
2024-02-20 14:04:31 +11:00
dogwithakeyboard
8b1d4ccc97 Movie scene sorting (#4588) 2024-02-20 14:01:27 +11:00
Raghavan
cff068f519 add pluginApi.d.ts (#4595)
* add pluginApi.d.ts
* Don't lint pluginApi.d.ts
2024-02-20 13:00:44 +11:00
WithoutPants
276bc5a8cb Fix 404 not returning immediately in asset resolver (#4597) 2024-02-20 13:00:27 +11:00
WithoutPants
b4a6cc43d1 Default tag hover popover to right (#4593) 2024-02-20 09:03:06 +11:00
WithoutPants
777fb44ac6 Fix default url not redirecting in studio page (#4592) 2024-02-20 09:02:52 +11:00
bob123491234
f5a42ede2d Add galleries to image edit panel (#4573)
* Add Galleries to ImageEditPanel
* Exclude filesystem-based galleries from selection
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-02-19 14:45:29 +11:00
WithoutPants
7bb38ae6dc Don't error out if a single url not fetched (#4591) 2024-02-19 14:45:11 +11:00
WithoutPants
7d56f1a093 Fix panic 2024-02-19 14:10:41 +11:00
WithoutPants
afd7f02644 Error if duplicate plugin id is loaded (#4571)
* Error if duplicate plugin id is loaded
* Use case insensitive comparison
2024-02-19 13:55:52 +11:00
WithoutPants
93b851eae6 Merge studio stash ids (#4572) 2024-02-19 11:45:04 +11:00
WithoutPants
1dfb960a87 Fix import 2024-02-19 11:43:14 +11:00
WithoutPants
e231812203 Movie select overhaul (#4563)
* Add ids to findMovies input
* Use ids for other find interfaces
* Update client side
* Fix gallery select function
* Replace movie select
* Re-add creatable
* Overhaul movie table
* Remove and deprecated unused code
2024-02-19 10:25:08 +11:00
WithoutPants
e7f610ce18 Fix invalid share causing error during cleaning (#4570) 2024-02-19 10:24:10 +11:00
WithoutPants
6e9718a600 Toast improvements (#4584)
* Change default toast placement
* Position at bottom on mobile
* Show single toast message at a time
* Optionally show dialog for error messages
* Fix circular dependency
* Animate toast
2024-02-19 10:22:34 +11:00
WithoutPants
6fb1c41ae9 Update translation files (#4581)
Updated by "Cleanup translation files" hook in Weblate.

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
2024-02-17 14:26:37 +11:00
WithoutPants
5aba3c1a98 Weblate translations update (#4578)
* Translated using Weblate (French)

Currently translated at 100.0% (1056 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 81.3% (859 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/pt_BR/

* Translated using Weblate (Swedish)

Currently translated at 96.3% (1017 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/

* Translated using Weblate (French)

Currently translated at 100.0% (1056 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (Swedish)

Currently translated at 99.8% (1054 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Translated using Weblate (French)

Currently translated at 100.0% (1056 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (French)

Currently translated at 100.0% (1056 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/fr/

* Translated using Weblate (Hungarian)

Currently translated at 37.5% (397 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/hu/

* Translated using Weblate (Swedish)

Currently translated at 99.9% (1055 of 1056 strings)

Translation: stashapp/stash
Translate-URL: https://hosted.weblate.org/projects/stashapp/stash/sv/

* Pretty locale files

* Sort locale keys

---------

Co-authored-by: doodoo <adr.web@hotmail.fr>
Co-authored-by: Robert de Abreu Viana <deathrobert2010@gmail.com>
Co-authored-by: alpacaserious <srhsgsef@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: R Balu <balu.ka@hotmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.com>
2024-02-17 14:06:40 +11:00
WithoutPants
440c261f5b Developer option: extra blob paths (#4566)
* Allow additional read-only blob paths
* Add developer option to add more blob sources
* Add makefile targets to start and remove build container
* Documentation
2024-02-16 12:39:45 +11:00
WithoutPants
8fc997dfe9 Fix merge not deleting deleted scene generated files (#4567) 2024-02-16 12:38:34 +11:00
WithoutPants
5b9bdadaec Mount edit panels only when first entering (#4564) 2024-02-16 12:37:49 +11:00
WithoutPants
706b61233f Fix country selector in bulk performer edit dialog (#4565)
* Fix country select in edit performers dialog
* Fix edit performer dialog height
2024-02-16 12:37:24 +11:00
yoshnopa
aaf3114194 Bugfix: No Image Upscale for Clips (#4569) 2024-02-16 12:21:36 +11:00
NodudeWasTaken
15aac68a14 Fix segment repeating + cleanup speed calculation (#4557) 2024-02-15 13:46:59 +11:00
WithoutPants
dad4ab6a6f Fix scraped studio not matching existing value (#4548)
* Fix scraped studio not matching existing
* Fix incorrect key value
2024-02-13 12:24:11 +11:00
WithoutPants
e9703e9a6e Fix url not updated for default tab on Performer/Studio/Tag pages (#4538) 2024-02-12 15:08:11 +11:00
WithoutPants
46eb01198a Plugin api improvements (#4546)
* Expose useToast

* Expose components:

- studio/tag/performer/gallery selects
- date input
- country select
- folder select
2024-02-12 15:07:44 +11:00
WithoutPants
235c9c90c2 Add disambiguation to performer link and performer select values (#4541)
* Add disambiguation to PerformerLink
* Add disambiguation to performer select values
2024-02-12 14:03:45 +11:00
WithoutPants
a4bbdcfbae Common studio overlay component (#4540)
* Move GridCard to own directory
* Make common studio overlay component
2024-02-12 14:03:13 +11:00
WithoutPants
8c410a9a14 Wrap card popovers (#4539) 2024-02-12 14:02:46 +11:00
WithoutPants
9981574e82 Add gallery select filter and fix image gallery filtering (#4535)
* Accept gallery ids in findGalleries
* Add gallery select component
* Add and fix image gallery filter
* Show gallery path as alias
2024-02-09 16:42:07 +11:00
WithoutPants
79e72ff3bc Fix UI config mutation not working (#4533) 2024-02-09 12:27:08 +11:00
CJ
a16f3da33e Fix tag popper over card (#4529) 2024-02-08 16:46:55 +11:00
WithoutPants
8770e81ec5 Improve sorting of results when entering text in select fields (#4528)
* Sort select results by relevance
* Apply relevance sorting to studio select
* Apply relevance sorting to filter select
2024-02-07 10:32:19 +11:00
dependabot[bot]
9284ede0fb Bump vite from 4.4.12 to 4.5.2 in /ui/v2.5 (#4477)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.12 to 4.5.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.2/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-07 10:30:17 +11:00
DingDongSoLong4
2d73912f15 Improve client-side graphql scalar types (#4511)
* Add types to graphql scalars
* Upgrade dependencies
* Override UI config type
* Remove all IUIConfig casts
* Add tableColumns to IUIConfig
* Add BoolMap type, set strictScalars
* Add PluginConfigMap
* Replace any with unknown
* Add SavedObjectFilter and SavedUIOptions
* Remove unused items from CriterionType
2024-02-07 09:49:32 +11:00
dogwithakeyboard
9ac6505241 Studio child filter and sort (#4479) 2024-02-06 13:24:00 +11:00
CJ
a402ee5fa7 support filesize in scene list (#4480) 2024-02-06 13:10:21 +11:00
CJ
a8df95c3a4 Fit cards properly within their containers (#4514)
* created missing cards grids
2024-02-06 13:06:47 +11:00
WithoutPants
330581283a Fix UI crash during setup (#4527) 2024-02-06 11:48:26 +11:00
Raghavan
892d74c98b expose useful libs (#4489)
* expose useful libs
* Update UIPluginApi.md
2024-02-06 11:29:01 +11:00
WithoutPants
de2b28d3f9 Studio select refactor (#4493)
* Add id interface to findStudios
* Replace existing selects
* Remove unused code
* Fix scrape/merge select
* Make clearable
2024-02-06 11:26:16 +11:00
WithoutPants
217c02f181 Fix tag selector in scrape dialogs (#4526) 2024-02-06 10:26:16 +11:00
DingDongSoLong4
3ea31aeb76 Add blank favicon on login page (#4498) 2024-02-06 10:22:32 +11:00
DingDongSoLong4
cf8efa9035 Link improvements and fixes (#4501)
* Add ExternalLink
* Replace <a> with <Link>
2024-02-06 10:21:19 +11:00
CJ
1d0fa27c71 Improve list table readability (#4497)
* fixes mandatory columns bug and consistency issues
2024-02-06 10:18:24 +11:00
NodudeWasTaken
0b82dbf666 Frontend fix media_err_decode on playback (#4506) 2024-02-06 10:08:40 +11:00
CJ
11cafe933a only use details background image when provided (#4515) 2024-01-29 18:04:41 +11:00
DingDongSoLong4
d82c526ada Minor UI fixes (#4509)
* Fix a zero value disabling the SettingModal
* Fix performer disambiguation styling
* Fix performer tagger error message
2024-01-29 17:56:39 +11:00
DogmaDragon
1588d1cb4e Update scene list setting (#4473) 2024-01-24 21:01:35 +11:00
DogmaDragon
64f2071d8c Add note for auto tag (#4490) 2024-01-24 21:00:57 +11:00
DogmaDragon
3573795cf7 Clarify session description (#4468) 2024-01-24 21:00:08 +11:00
WithoutPants
723211a620 Tag select refactor (#4478)
* Add interface to load tags by id
* Use minimal data for tag select queries
* Center image/text in select list
* Overhaul tag select
* Support excludeIds. Comment out image in dropdown
* Replace existing selects
* Remove unused code
* Fix styling of aliases
2024-01-24 20:24:13 +11:00
CJ
dd8da7f339 Improved list view for scenes, galleries and performers (#4368)
Co-authored-by: InfiniteStash <117855276+InfiniteStash@users.noreply.github.com>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-01-17 10:46:09 +11:00
WithoutPants
e7311a60d2 Advanced settings (#4378)
* Add advanced settings mode toggle
* Add advanced settings
2024-01-16 16:22:04 +11:00
flubber1234
29677696fd Add support for disabling mobile media-viewer's fullscreen auto-rotate (#4416) 2024-01-16 15:00:58 +11:00
WithoutPants
403f7c54ef Move zip files while moving folders (#4374) 2024-01-16 14:48:28 +11:00
OFP
75099b38a8 Update Performer gender filter (#4419)
* Update Performer gender filter

Updated the Performer gender filter to allow selection of multiple genders (`IS`) or performers with no gender specified (`IS NULL`).

* Add default modifier for circumcised
* Handle existing saved filters
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-01-16 14:47:33 +11:00
CJ
45e2e12594 Improved scene queue (#4448)
* Improved scene queue
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-01-16 14:40:44 +11:00
dependabot[bot]
ec547e8d30 Bump vite from 4.4.11 to 4.4.12 in /ui/v2.5 (#4348)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.4.11 to 4.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.4.12/packages/vite)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-16 13:59:17 +11:00
dependabot[bot]
e470dc5f52 Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#4375)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-16 13:50:41 +11:00
keenbed
14bde44597 added support for image orientation filter (#4404)
* added support for image orientation filter
* Add orientation filtering to scenes
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2024-01-16 13:50:17 +11:00
WithoutPants
aeb68a5851 Update changelog 2024-01-15 10:34:59 +11:00
WithoutPants
5cf28cf8af Fix studio name uniqueness validation (#4454) 2024-01-14 12:52:16 +11:00
DingDongSoLong4
08b73581a6 Don't retry failed autoplay (#4450) 2024-01-13 10:50:38 +11:00
WithoutPants
95a2c8d13f Update changelog for bugfix release 2024-01-10 11:21:06 +11:00
DingDongSoLong4
0b131f76df Fix scene marker merging (#4446) 2024-01-10 10:25:05 +11:00
WithoutPants
6271f18979 Fix error when creating/updating performer with alias == name (#4443)
* Filter out performer aliases that match the name
* Validate when creating/updating performer in stash-box task
2024-01-09 14:57:49 +11:00
WithoutPants
ca976a0994 Don't bail on error when scraping all (#4442) 2024-01-09 11:39:00 +11:00
DingDongSoLong4
9859ec61fb Calculate DetailImage fallback width using rem (#4441) 2024-01-09 11:11:46 +11:00
DingDongSoLong4
a998497004 Hide tag input when set tags is disabled (#4440) 2024-01-09 11:09:42 +11:00
bayured
f5e3fe77b7 Update FieldStrategyOverwrite to work when scene has no existing URL (#4412) 2024-01-09 10:23:29 +11:00
WithoutPants
743ab9a52c Sort plugin settings (#4435) 2024-01-09 09:32:26 +11:00
WithoutPants
d23cecfc18 Disable select all checkbox for plugin sources (#4434) 2024-01-09 09:32:16 +11:00
DingDongSoLong4
d8990e655d Fix settings tab links (#4430) 2024-01-08 12:08:09 +11:00
DingDongSoLong4
5b9a96b843 Scene queue autoplay (#4428)
* Remove unnecessary undefined checks
* Respect autostartVideoOnPlaySelected in scene queue
2024-01-08 12:04:30 +11:00
CJ
b968aa3f31 Fixes package manger head border (#4420) 2024-01-08 11:54:14 +11:00
DingDongSoLong4
910c7025dc Fix scraped performer alias matching (#4432) 2024-01-08 11:50:31 +11:00
bayured
ea503833c5 Add join to intCriterionHandler (#4414)
* Add join to intCriterionHandler
* Add join to floatCriterionHandler
2024-01-08 11:48:16 +11:00
cc1234475
6848dec5f4 Add CSP to plugin from the yaml file (#4424) 2024-01-08 11:45:55 +11:00
WithoutPants
bd7d4ac7ff Update changelog for bugfix release 2023-12-29 14:29:49 +11:00
WithoutPants
5a6504b4ba Workaround setting protocol on external player url (#4403) 2023-12-29 14:26:30 +11:00
WithoutPants
f8a93789bb Use 8.14.3-r0 of vips (#4402) 2023-12-28 18:34:34 +11:00
WithoutPants
82cbeff9b5 Revert modal button order change (#4400) 2023-12-28 13:56:05 +11:00
DingDongSoLong4
f32d60f208 Update macOS readme links (#4347)
* Update readme macOS download links

* Update docker readme to point to develop branch
2023-12-27 10:57:28 +02:00
DingDongSoLong4
3e9bd8507f Fix Scene Tagger config blacklist (#4396)
* Refactoring
* Add item on Enter
* Don't add duplicate items
2023-12-27 11:02:43 +11:00
DingDongSoLong4
6ee7e6112b Refresh package managers after setup (#4397)
* Refresh package managers after setup
* Fix default plugins/scrapers paths
2023-12-27 10:57:10 +11:00
DingDongSoLong4
9bd36408ee Fix saved filter UI bugs (#4394)
* Fix missing intl strings
* Fix saved filter list z-index
* Fix saved filter list double scrollbar
* Display error inside filter list
* Filter out nonexistent saved filter rows in FrontPageConfig
2023-12-26 16:03:55 +11:00
DingDongSoLong4
0cdea209bb Fix stats page SUM error (#4390) 2023-12-22 12:40:05 +02:00
WithoutPants
e1782d094d Ensure plugin scripts are loaded in correct order (#4388) 2023-12-22 15:10:21 +11:00
WithoutPants
d258976358 Update changelog 2023-12-22 14:13:29 +11:00
WithoutPants
afda6decf2 Support setting file fingerprints (#4376)
* Support setting file fingerprints
* Disallow modifying managed hashes
2023-12-22 14:07:10 +11:00
DingDongSoLong4
a1bd7cf817 Package manager UI-related tweaks (#4382)
* Add Plugins Path setting
* Fix/improve cache invalidation
* Hide load error when collapsing package source
* Package manager style tweaks
* Show error if installed packages query failed
* Prevent "No packages found" flicker
* Show <unknown> if empty version
* Always show latest version, highlight if new version available
* Fix issues with non-unique cross-source package ids
* Don't wrap id, version and date
* Decrease collapse button padding
* Display description for scraper packages
* Fix default packages population
* Change default package path to community
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-12-22 14:05:53 +11:00
WithoutPants
23b4d4f1e0 Show confirmation when deleting via keybind (#4387) 2023-12-22 10:07:02 +11:00
DingDongSoLong4
d0d0d1e11f Include image total O-Count on stats page (#4386) 2023-12-21 16:19:41 +11:00
WithoutPants
e304d981d0 Add changelog for 0.24.0 (#4380) 2023-12-20 13:32:35 +11:00
Maista
e8af3c8e98 Set PYTHONPATH environment variable for Python script scrapers (#4372)
* Set PYTHONPATH environment variable for Python script scrapers
* Convert PYTHONPATH to absolute
* Generalise and apply to plugins

---------

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-12-20 13:32:19 +11:00
WithoutPants
8c922ed9e1 Fix studio svg scaling (#4371) 2023-12-20 13:06:57 +11:00
WithoutPants
c9501ef881 Default package sources (#4364)
* Set default package sources
* Add release notes
* Add documentation
* Index URL -> Source URL
2023-12-18 09:45:21 +11:00
DingDongSoLong4
26c3873122 Use int64 for sample-related FFProbeStream fields (#4361) 2023-12-14 13:31:58 +11:00
DingDongSoLong4
2ef2d89b06 Fix desktop notification duration formatting (#4358) 2023-12-14 13:31:20 +11:00
DingDongSoLong4
43a9df8621 Fix CI pip externally-managed-environment error (#4360)
* Add missing Makefile PHONY target
* Add Dockerfile-CUDA vim modeline
* Fix CI pip externally-managed-environment error
2023-12-14 13:29:44 +11:00
Emilo2
7a9cb548ab Merge url's in scene tagger instead of replacing (#4343) 2023-12-12 11:47:48 +11:00
DingDongSoLong4
74ddfa47e9 Refactor and fix stashbox submit dialog (#4355) 2023-12-12 11:45:52 +11:00
DingDongSoLong4
d37de0e49b Fix crash on blank aliases/urls (#4344)
* Fix crash on blank alias/url
* Fix StringListInput clear issue
2023-12-12 11:28:00 +11:00
WithoutPants
eca5838ce0 Add freebsd to releases (#4341) 2023-12-04 14:27:34 +11:00
DingDongSoLong4
ccb1b7c3c4 Fix exit from systray (#4337) 2023-12-02 17:44:20 +02:00
DingDongSoLong4
d4ef182871 Remove railsTimeLayout from ParseDateStringAsTime (#4333) 2023-12-01 22:28:38 +02:00
DingDongSoLong4
d24b52ae7f Fix nil deference when generating markers by ID (#4335) 2023-12-01 22:28:18 +02:00
anonymous-ants
46e1715a59 Update MacOS dev preview URL (#4330) 2023-11-30 21:03:03 +02:00
WithoutPants
5ebd3b3568 Fix issues navigating scene queue (#4325)
* Fix issues navigating between queue scenes
* Fix queue next/previous button display
* Autplay when clicking scene in queue
* Sticky queue controls
2023-11-30 10:45:45 +11:00
vt-idiot
54461aa140 Update vrmode.ts (#3799)
* Update vrmode.ts

Enabling the most common VR projection (180_LR) and an older but no longer used one (360_TB) in the UI.

* Downgrade videojs-vr
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-11-29 15:01:35 +11:00
WithoutPants
1659c8f185 Fix navbar styling on smaller viewports (#4324) 2023-11-29 12:38:39 +11:00
WithoutPants
599deb71b6 Use visual files to populate image file names (#4323) 2023-11-29 12:38:28 +11:00
bob123491234
413311711f Add Details, Studio Code, and Photographer to Images (#4217)
* Add Details, Code, and Photographer to Images
* Add date and details to image card
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-11-28 15:45:07 +11:00
bob123491234
d1018b4c5d Add Studio Code and Photographer to Galleries. (#4195)
* Added Studio Code and Photographer to Galleries
* Fix gallery display on mobile
* Fixed potential panic when scraping with a bad configuration
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-11-28 15:05:33 +11:00
DingDongSoLong4
b78771dbcd Manager refactor, part 1 (#4298)
* Move BackupDatabase and AnonymiseDatabase to internal/manager
* Rename config.Instance to config.Config
* Rename FFMPEG
* Rework manager and initialization process
* Fix Makefile
* Tweak phasher
* Fix config races
* Fix setup error not clearing
2023-11-28 13:56:46 +11:00
DingDongSoLong4
fc1fc20df4 Setup tweaks (#4304) 2023-11-28 13:56:07 +11:00
WithoutPants
b915428f06 UI Plugin API (#4256)
* Add page registration
* Add example plugin
* First version of proper react plugins
* Make reference react plugin
* Add patching functions
* Add tools link poc
* NavItem poc
* Add loading hook for lazily loaded components
* Add documentation
2023-11-28 13:06:44 +11:00
WithoutPants
11be56cc42 UI plugin dependencies (#4307)
* Add requires field to UI plugin config
* Use defer instead of async for useScript
* Load plugins based on dependency
* Document new field
2023-11-27 13:41:04 +11:00
WithoutPants
910ff27730 Convert legacy parsed date to UTC 2023-11-27 13:05:29 +11:00
WithoutPants
11a1e49292 Remove timezone from package date format (#4313)
* Use UTC date for manifest
* Use intl to format package dates
2023-11-27 12:13:01 +11:00
DingDongSoLong4
0e7c58a5a6 Fix gallery export panic (#4311)
* Load URLs when exporting galleries
* Remove err.Error()
* Log gallery DisplayName rather than PrimaryChecksum
2023-11-27 10:43:34 +11:00
WithoutPants
a8140c11ec Cache package list in memory instead of filesystem (#4309) 2023-11-23 14:16:13 +11:00
Maista
0dcd58763f Log more when resolving Python (#4185)
* Log more when resolving Python

Users often have problems configuring their Python installations

* Convert if-else ladder to switch statement
* Consolidate Python resolution

Adds additional logging to plugin tasks to
align with the logging that scrapers output.
2023-11-22 10:04:15 +11:00
WithoutPants
987fa80786 Scraper and plugin manager (#4242)
* Add package manager
* Add SettingModal validate
* Reverse modal button order
* Add plugin package management
* Refactor ClearableInput
2023-11-22 10:01:11 +11:00
elkorol
d95ef4059a Added various tooltips (#4264) 2023-11-22 09:53:05 +11:00
DingDongSoLong4
13a24a634d Ignore mousemove when just clicking (#4302) 2023-11-22 09:52:36 +11:00
DingDongSoLong4
b0a34a3dc0 Update compiler image in golangci-lint workflow 2023-11-21 22:40:49 +02:00
DingDongSoLong4
343660c378 Fix build Dockerfile after #4153 2023-11-21 22:08:00 +02:00
DingDongSoLong4
611a1e7854 Fix Ubuntu dependencies (#4299) 2023-11-20 14:43:49 +11:00
DingDongSoLong4
2ea35c4dbc Minor UI tweaks (#4297)
* Don't render PluginTasks if no tasks available
* Improve query refetching
2023-11-20 14:19:43 +11:00
DingDongSoLong4
f9e11813f0 Toast fixes/refactoring (#4289)
* Memoize Toast functions
* Rename Toast.success to Toast.toast, add new Toast.success
* Disable prefer-destructuring on AssignmentExpression
2023-11-20 14:14:34 +11:00
DingDongSoLong4
049a1b15c3 Add fingerprint resolver (#4287)
* Refactor into internal/api/models.go
* Add file fingerprint resolver
2023-11-20 13:09:12 +11:00
InfiniteStash
a0f33e3dab Add filtering to folder select (#4277) 2023-11-20 13:00:28 +11:00
yer2
61f4d8bd12 Duplicate checker option for selecting highest resolution (#4286)
* Added duplicate checker dropdown menu option to allow selecting all except highest resolution.
2023-11-20 12:54:59 +11:00
DingDongSoLong4
959f2531fd Form-related fixes, improvements and refactoring (#4283)
* Fix another validateDOMNesting error
* Fix React.forwardRef error
* Fix encoding_image intl message
* Return null instead of undefined from RatingSystem
* DurationInput tweaks
* DateInput tweaks, remove unused utils functions
* Refactor and deduplicate edit form rendering
* Improve/fix yup validation
2023-11-20 12:42:26 +11:00
DingDongSoLong4
65b416a2d9 Fix FreeBSD cross-compilation (#4251)
* Cleanup compiler container
* Fix FreeBSD cross-compilation
* Bump compiler version
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-11-20 11:26:57 +11:00
WithoutPants
222475df82 Plugin assets, external scripts and CSP overrides (#4260)
* Add assets for plugins
* Move plugin javascript and css into separate endpoints
* Allow loading external scripts
* Add csp overrides
* Only include enabled plugins
* Move URLMap to utils
* Use URLMap for assets
* Add documentation
2023-11-19 10:41:16 +11:00
DingDongSoLong4
4dd4c3c658 Improved/fixed macOS support (#4153)
* Fix macOS notifications
* Change CFBundleIdentifier to match domain
* Distribute Stash.app
* Also build universal phasher binary
* Fix binary name in check_version.go
* Expose GOOS, working dir and home dir in systemStatus endpoint
* Disable setup in working directory when running Stash.app
* More Makefile improvements, remove unused scripts
* Improve READMEs and documentation
2023-11-19 10:36:13 +11:00
DingDongSoLong4
72779e618d Fix batch performer tag panic (#4281) 2023-11-12 03:50:09 +02:00
DingDongSoLong4
4715c5ebb2 Fix performer validation (#4248)
* Fix performer validation
* Add tests
* Rename QueryCount argument
* Minor refactoring
* Add duplicate alias validation
* Make UI alias validation also case-insensitive
2023-11-02 16:23:54 +11:00
DingDongSoLong4
d96558704a Improve random sorting algorithm (#4246) 2023-11-02 16:13:56 +11:00
elkorol
795af64e8e Fixed nesting anchor elements (#4255) 2023-11-02 15:58:03 +11:00
DingDongSoLong4
9621213424 Genericise sliceutil functions (#4253)
* Genericise sliceutil.SliceSame
* Genericise intslice functions
* Genericise stringutil functions
2023-11-02 08:58:32 +11:00
Ian McKenzie
cc6673f276 Add OpenBSD development instructions (#4243) 2023-10-26 16:26:33 +11:00
its-josh4
2b8c2534dd Update a number of dependencies (incl. CVE fixes) (#4107)
* Update a number of dependencies (incl. CVE fixes)

Includes some dependencies that were upgraded in #4106 as well as a few more dependencies.

Some deps that have been upgraded had CVEs.

Notably, upgrades deprecated dependencies such as:
- `github.com/go-chi/chi` (replaced with `/v5`)
- `github.com/gofrs/uuid` (replaced with `/v5`)
- `github.com/hashicorp/golang-lru` (replaced with `/v2` which uses generics)

* Upgraded a few more deps

* lint

* reverted yaml library to v2

* remove unnecessary mod replace

* Update chromedp

Fixes #3733
2023-10-26 16:24:32 +11:00
WithoutPants
552f86586a Show performer image in select (#4227) 2023-10-26 15:01:49 +11:00
DingDongSoLong4
c4d7a7ab2c Accept incorrectly insensitivised frontpage config keys (#4237) 2023-10-26 15:01:11 +11:00
WithoutPants
298f3d4e19 Lazy load images (#4228)
* Add lazy loading for many images
* Load sprites on first hover of scrubber
2023-10-23 16:52:56 +11:00
DingDongSoLong4
87bdbb2058 Vite dev server authentication tweaks (#4234)
* Add VITE_APP_PLATFORM_URL, error on dev server auth
* Remove experimentalDeepDynamicChunkOptimization
2023-10-23 16:52:02 +11:00
its-josh4
b99d16b712 Some cosmetic fixes from #4106 (#4236) 2023-10-23 08:20:41 +11:00
DingDongSoLong4
24984da16e Fix UI config conversion yet again (#4128) 2023-10-19 10:21:03 +11:00
WithoutPants
2b8718100b Plugin settings (#4143)
* Add backend support for plugin settings
* Add plugin settings config
* Add UI support for plugin settings
2023-10-18 14:09:13 +11:00
DingDongSoLong4
06d8353f4f Fix tagger result styling (#4222)
* Fix tagger result styling
* Fix DEFAULT_SLIDESHOW_DELAY
2023-10-18 13:14:11 +11:00
dependabot[bot]
939bb422d1 Bump @babel/traverse from 7.20.13 to 7.23.2 in /ui/v2.5 (#4226)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.13 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 13:09:06 +11:00
WithoutPants
339b9fcc16 Upgrade vite to fix ui-start issues (#4225) 2023-10-18 12:29:20 +11:00
WithoutPants
a83dfff5ff Show stash-ids with their endpoint (#4216) 2023-10-18 07:56:49 +11:00
Ian McKenzie
21baa23fc5 Fix unix build flags to be friendly to niche operating systems (#4214) 2023-10-17 12:00:19 +11:00
WithoutPants
b6714fafba Remove deprecated graphql fields (#4064)
* Remove deprecated list*Scrapers queries
* Remove other deprecated query resolvers
* Remove deprecated config fields
* Remove deprecated gallery fields
* Remove deprecated image fields
* Remove deprecated movie fields
* Remove deprecated performer fields
* Document scrape function issue
* Remove deprecated studio fields
* Remove deprecated scan input fields
* Remove deprecated scene fields
* Remove deprecated fields from filters
* Remove scene.file_mod_time
2023-10-17 11:59:35 +11:00
WithoutPants
a9ab1fcca7 Fix performer stash ids being overwritten in performer tagger (#4215) 2023-10-17 11:42:57 +11:00
WithoutPants
5e0f27bed2 Don't unset organized if tagger flag is false (#4213) 2023-10-17 09:38:32 +11:00
Flashy78
789de2d5f6 Errors for performer/studio non-unique names (#4178)
* Errors for performer/studio non-unique names
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-10-16 17:26:43 +11:00
WithoutPants
2fd7141f0f Javascript scraper postprocess (#4200)
* Add javascript post-process action
* Add documentation
2023-10-16 17:17:36 +11:00
bob123491234
bdf705fe7c Fix images total megapixels and filesize queries (#4203) 2023-10-16 17:16:40 +11:00
Flashy78
2ec948a836 Add disambiguation and links to tagger results (#4180)
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2023-10-16 16:35:08 +11:00
dogwithakeyboard
7605eec6da scene framerate filter (#4161) 2023-10-16 16:28:28 +11:00
0xb0af
8eb069054e improve tooltip (#4198) 2023-10-16 16:16:53 +11:00
WithoutPants
b6808dc714 Add support for disabling plugins (#4141)
* Move timestamp to own file
* Backend changes
* UI changes
2023-10-16 16:15:12 +11:00
Flashy78
e5af37efbc Tagger match performer by alias (#4182) 2023-10-16 14:39:41 +11:00
InfiniteStash
409f8fc70c Fix type issues (#4176) 2023-10-16 14:34:54 +11:00
dependabot[bot]
90dfaf668b Bump graphql from 16.6.0 to 16.8.1 in /ui/v2.5 (#4140)
Bumps [graphql](https://github.com/graphql/graphql-js) from 16.6.0 to 16.8.1.
- [Release notes](https://github.com/graphql/graphql-js/releases)
- [Commits](https://github.com/graphql/graphql-js/compare/v16.6.0...v16.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 14:29:40 +11:00
dependabot[bot]
bc261f789a Bump postcss from 8.4.25 to 8.4.31 in /ui/v2.5 (#4179)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.25 to 8.4.31.
- [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/8.4.25...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 14:29:00 +11:00
dependabot[bot]
9552273478 Bump golang.org/x/net from 0.7.0 to 0.17.0 (#4192)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-16 14:28:33 +11:00
DingDongSoLong4
33f2ebf2a3 Model refactor, part 3 (#4152)
* Remove manager.Repository
* Refactor other repositories
* Fix tests and add database mock
* Add AssertExpectations method
* Refactor routes
* Move default movie image to internal/static and add convenience methods
* Refactor default performer image boxes
2023-10-16 14:26:34 +11:00
Emilo2
40bcb4baa5 Fix url's from gallery scrapers (#4206)
* Fill in urls array when gallery scraper provides only single url
2023-10-16 13:27:42 +11:00
WithoutPants
479e716385 Update changelog for bugfix release 2023-10-14 10:46:49 +11:00
WithoutPants
06c9d6f554 Fix performer select not working correctly in scrape dialog (#4199) 2023-10-13 15:43:23 +11:00
RPaetau
1f0f5eb49c slideshow delay default values changed from 5000 to 5 seconds (#4186)
* Update Lightbox.tsx to also change default delay here to 5 sec instead of 5000
* Update config.go to set default slideshow delay from 5000 sec to 5 sec
2023-10-10 14:22:39 +11:00
Emilo2
c109a58231 Fix gallery url scraping (#4187) 2023-10-10 14:08:48 +11:00
1281 changed files with 137968 additions and 62140 deletions

View File

@@ -17,7 +17,7 @@
# GraphQL generated output
pkg/models/generated_*.go
ui/v2.5/src/core/generated-*.tsx
ui/v2.5/src/core/generated-graphql.ts
####
# Jetbrains

12
.github/FUNDING.yml vendored
View File

@@ -1,12 +0,0 @@
# These are supported funding model platforms
github: stashapp
# patreon: # Replace with a single Patreon username
open_collective: stashapp
# ko_fi: # Replace with a single Ko-fi username
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
# liberapay: StashApp
# issuehunt: # Replace with a single IssueHunt username
# otechie: # Replace with a single Otechie username
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -1,40 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug Report] Short Form Subject (50 Chars or less)"
labels: bug report
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem please ensure that your screenshots are SFW or at least appropriately censored.
**Stash Version: (from Settings -> About):**
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

64
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: Bug Report
description: Create a report to help us fix the bug
labels: ["bug report"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: description
attributes:
label: Describe the bug
description: Provide a clear and concise description of what the bug is.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Detail the steps that would replicate this issue.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behaviour
description: Provide clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: context
attributes:
label: Screenshots or additional context
description: Provide any additional context and SFW screenshots here to help us solve this issue.
validations:
required: false
- type: input
id: stashversion
attributes:
label: Stash version
description: This can be found in Settings > About.
placeholder: (e.g. v0.28.1)
validations:
required: true
- type: input
id: devicedetails
attributes:
label: Device details
description: |
If this is an issue that occurs when using the Stash interface, please provide details of the device/browser used which presents the reported issue.
placeholder: (e.g. Firefox 97 (64-bit) on Windows 11)
validations:
required: false
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output from Settings > Logs. This will be automatically formatted into code, so no need for backticks.
render: shell

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Community forum
url: https://discourse.stashapp.cc
about: Start a discussion on the community forum.
- name: Community Discord
url: https://discord.gg/Y8MNsvQBvZ
about: Chat with the community on Discord.
- name: Documentation
url: https://docs.stashapp.cc
about: Check out documentation for help and information.

View File

@@ -1,24 +0,0 @@
---
name: Discussion / Request for Commentary [RFC]
about: This is for issues that will be discussed and won't necessarily result directly
in commits or pull requests.
title: "[RFC] Short Form Title"
labels: help wanted
assignees: ''
---
<!-- Update or delete the title if you need to delegate your title gore to something
# Title
*### Scope*
<!-- describe the scope of your topic and your goals ideally within a single paragraph or TL;DR kind of summary so its easier for people to determine if they can contribute at a glance. -->
## Long Form
<!-- Only required if your scope and titles can't cover everything. -->
## Examples
<!-- if you can show a picture or video examples post them here, please ensure that you respect people's time and attention and understand that people are volunteering their time, so concision is ideal and considerate. -->
## Reference Reading
<!-- if there is any reference reading or documentation, please refer to it here. -->

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature] Short Form Title (50 chars or less.)"
labels: feature request
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,44 @@
name: Feature Request
description: Request a new feature or idea to be added to Stash
labels: ["feature request"]
body:
- type: textarea
id: description
attributes:
label: Describe the feature you'd like
description: Provide a clear description of the feature you'd like implemented
validations:
required: true
- type: textarea
id: benefits
attributes:
label: Describe the benefits this would bring to existing users
description: |
Explain the measurable benefits this feature would achieve for existing users.
The benefits should be described in terms of outcomes for users, not specific implementations.
validations:
required: true
- type: textarea
id: already_possible
attributes:
label: Is there an existing way to achieve this goal?
description: |
Yes/No. If Yes, describe how your proposed feature differs from or improves upon the current method
validations:
required: true
- type: checkboxes
id: confirm-search
attributes:
label: Have you searched for an existing open/closed issue?
description: |
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/stashapp/stash/issues?q=is%3Aissue) for any existing issues that cover the core request or benefit of your proposal.
options:
- label: I have searched for existing issues and none cover the core request of my proposal
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false

View File

@@ -2,7 +2,10 @@ name: Build
on:
push:
branches: [ develop, master ]
branches:
- develop
- master
- 'releases/**'
pull_request:
release:
types: [ published ]
@@ -12,17 +15,22 @@ concurrency:
cancel-in-progress: true
env:
COMPILER_IMAGE: stashapp/compiler:7
COMPILER_IMAGE: stashapp/compiler:12
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- name: Checkout
run: git fetch --prune --unshallow --tags
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Pull compiler image
run: docker pull $COMPILER_IMAGE
@@ -32,7 +40,7 @@ jobs:
cache-name: cache-node_modules
with:
path: ui/v2.5/node_modules
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock') }}
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/pnpm-lock.yaml') }}
- name: Cache UI build
uses: actions/cache@v3
@@ -41,7 +49,7 @@ jobs:
cache-name: cache-ui
with:
path: ui/v2.5/build
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/yarn.lock', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('ui/v2.5/pnpm-lock.yaml', 'ui/v2.5/public/**', 'ui/v2.5/src/**', 'graphql/**/*.graphql') }}
- name: Cache go build
uses: actions/cache@v3
@@ -60,7 +68,7 @@ jobs:
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"
run: docker exec -t build /bin/bash -c "make CI=1 pre-ui"
- name: Generate
run: docker exec -t build /bin/bash -c "make generate"
@@ -84,13 +92,16 @@ 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-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-linux-arm32v6"
docker exec -t build /bin/bash -c "make build-cc-windows"
docker exec -t build /bin/bash -c "make build-cc-macos"
docker exec -t build /bin/bash -c "make build-cc-linux"
docker exec -t build /bin/bash -c "make build-cc-linux-arm64v8"
docker exec -t build /bin/bash -c "make build-cc-linux-arm32v7"
docker exec -t build /bin/bash -c "make build-cc-linux-arm32v6"
docker exec -t build /bin/bash -c "make build-cc-freebsd"
- name: Zip UI
run: docker exec -t build /bin/bash -c "make zip-ui"
- name: Cleanup build container
run: docker rm -f -v build
@@ -98,34 +109,42 @@ jobs:
- name: Generate checksums
run: |
git describe --tags --exclude latest_develop | tee CHECKSUMS_SHA1
sha1sum dist/stash-* | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
sha1sum dist/Stash.app.zip dist/stash-* dist/stash-ui.zip | sed 's/dist\///g' | tee -a CHECKSUMS_SHA1
echo "STASH_VERSION=$(git describe --tags --exclude latest_develop)" >> $GITHUB_ENV
echo "RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z')" >> $GITHUB_ENV
- name: Upload Windows binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: stash-win.exe
path: dist/stash-win.exe
- name: Upload OSX binary
- name: Upload macOS binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: stash-macos-intel
path: dist/stash-macos-intel
name: stash-macos
path: dist/stash-macos
- name: Upload Linux binary
# only upload binaries for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: stash-linux
path: dist/stash-linux
- name: Upload UI
# only upload for pull requests
if: ${{ github.event_name == 'pull_request' && github.base_ref != 'refs/heads/develop' && github.base_ref != 'refs/heads/master'}}
uses: actions/upload-artifact@v4
with:
name: stash-ui.zip
path: dist/stash-ui.zip
- name: Update latest_develop tag
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
run : git tag -f latest_develop; git push -f --tags
@@ -139,13 +158,15 @@ jobs:
automatic_release_tag: latest_develop
title: "${{ env.STASH_VERSION }}: Latest development build"
files: |
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/Stash.app.zip
dist/stash-macos
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-linux-arm32v6
dist/stash-freebsd
dist/stash-ui.zip
CHECKSUMS_SHA1
- name: Master release
@@ -157,13 +178,15 @@ jobs:
token: "${{ secrets.GITHUB_TOKEN }}"
allow_override: true
files: |
dist/stash-macos-intel
dist/stash-macos-applesilicon
dist/Stash.app.zip
dist/stash-macos
dist/stash-win.exe
dist/stash-linux
dist/stash-linux-arm64v8
dist/stash-linux-arm32v7
dist/stash-linux-arm32v6
dist/stash-freebsd
dist/stash-ui.zip
CHECKSUMS_SHA1
gzip: false

View File

@@ -6,10 +6,11 @@ on:
branches:
- master
- develop
- 'releases/**'
pull_request:
env:
COMPILER_IMAGE: stashapp/compiler:7
COMPILER_IMAGE: stashapp/compiler:12
jobs:
golangci:
@@ -21,6 +22,11 @@ jobs:
- name: Checkout
run: git fetch --prune --unshallow --tags
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Pull compiler image
run: docker pull $COMPILER_IMAGE
@@ -33,7 +39,7 @@ jobs:
run: docker exec -t build /bin/bash -c "make generate-backend"
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
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: latest

10
.gitignore vendored
View File

@@ -21,10 +21,8 @@ vendor
# GraphQL generated output
internal/api/generated_*.go
####
# Jetbrains
####
# Generated locale files
ui/login/locales/*
####
# Visual Studio
@@ -52,9 +50,6 @@ internal/api/generated_*.go
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Goland Junk
pkg/pkg
####
# Random
####
@@ -64,6 +59,7 @@ node_modules
*.db
/stash
/Stash.app
/phasher
dist
.DS_Store

View File

@@ -15,11 +15,11 @@ linters:
- unused
# Linters added by the stash project.
# - contextcheck
- copyloopvar
- dogsled
- errchkjson
- errorlint
# - exhaustive
- exportloopref
- gocritic
# - goerr113
- gofmt
@@ -48,8 +48,6 @@ linters-settings:
ignore-generated-header: true
severity: error
confidence: 0.8
error-code: 1
warning-code: 1
rules:
- name: blank-imports
disabled: true

View File

@@ -1,14 +1,12 @@
model:
package: graphql
filename: ./pkg/scraper/stashbox/graphql/generated_models.go
filename: ./pkg/stashbox/graphql/generated_models.go
client:
package: graphql
filename: ./pkg/scraper/stashbox/graphql/generated_client.go
filename: ./pkg/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.

3
.idea/go.iml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/certs" />
@@ -10,4 +11,4 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

345
Makefile
View File

@@ -9,9 +9,11 @@ endif
ifdef IS_WIN_SHELL
RM := del /s /q
RMDIR := rmdir /s /q
NOOP := @@
else
RM := rm -f
RMDIR := rm -rf
NOOP := @:
endif
# set LDFLAGS environment variable to any extra ldflags required
@@ -36,7 +38,7 @@ GO_BUILD_FLAGS := $(GO_BUILD_FLAGS)
# set GO_BUILD_TAGS environment variable to any extra build tags required
GO_BUILD_TAGS := $(GO_BUILD_TAGS)
GO_BUILD_TAGS += sqlite_stat4
GO_BUILD_TAGS += sqlite_stat4 sqlite_math_functions
# set STASH_NOLEGACY environment variable or uncomment to disable legacy browser support
# STASH_NOLEGACY := true
@@ -46,33 +48,49 @@ GO_BUILD_TAGS += sqlite_stat4
export CGO_ENABLED := 1
# define COMPILER_IMAGE for cross-compilation docker container
ifndef COMPILER_IMAGE
COMPILER_IMAGE := stashapp/compiler:latest
endif
.PHONY: release
release: pre-ui generate ui build-release
# targets to set various build flags
# use combinations on the make command-line to configure a build, e.g.:
# for a static-pie release build: `make flags-static-pie flags-release stash`
# for a static windows debug build: `make flags-static-windows stash`
# $(NOOP) prevents "nothing to be done" warnings
.PHONY: flags-release
flags-release:
flags-release:
$(NOOP)
$(eval LDFLAGS += -s -w)
$(eval GO_BUILD_FLAGS += -trimpath)
.PHONY: flags-pie
flags-pie:
$(NOOP)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
.PHONY: flags-static
flags-static:
flags-static:
$(NOOP)
$(eval LDFLAGS += -extldflags=-static)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)
.PHONY: flags-static-pie
flags-static-pie:
flags-static-pie:
$(NOOP)
$(eval LDFLAGS += -extldflags=-static-pie)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo netgo)
# identical to flags-static-pie, but excluding netgo, which is not needed on windows
.PHONY: flags-static-windows
flags-static-windows:
$(NOOP)
$(eval LDFLAGS += -extldflags=-static-pie)
$(eval GO_BUILD_FLAGS += -buildmode=pie)
$(eval GO_BUILD_TAGS += sqlite_omit_load_extension osusergo)
@@ -105,166 +123,141 @@ build-flags: build-info
stash: build-flags
go build $(STASH_OUTPUT) $(BUILD_FLAGS) ./cmd/stash
.PHONY: stash-release
stash-release: flags-release
stash-release: flags-pie
stash-release: stash
.PHONY: stash-release-static
stash-release-static: flags-release
stash-release-static: flags-static-pie
stash-release-static: stash
.PHONY: stash-release-static-windows
stash-release-static-windows: flags-release
stash-release-static-windows: flags-static-windows
stash-release-static-windows: stash
.PHONY: phasher
phasher: build-flags
go build $(PHASHER_OUTPUT) $(BUILD_FLAGS) ./cmd/phasher
.PHONY: phasher-release
phasher-release: flags-release
phasher-release: flags-pie
phasher-release: phasher
.PHONY: phasher-release-static
phasher-release-static: flags-release
phasher-release-static: flags-static-pie
phasher-release-static: phasher
.PHONY: phasher-release-static-windows
phasher-release-static-windows: flags-release
phasher-release-static-windows: flags-static-windows
phasher-release-static-windows: phasher
# builds dynamically-linked debug binaries
.PHONY: build
build: stash phasher
# builds dynamically-linked release binaries
# builds dynamically-linked PIE release binaries
.PHONY: build-release
build-release: stash-release phasher-release
build-release: flags-release flags-pie build
# builds statically-linked release binaries
.PHONY: build-release-static
build-release-static: stash-release-static phasher-release-static
# compile and bundle into Stash.app
# for when on macOS itself
.PHONY: stash-macapp
stash-macapp: STASH_OUTPUT := -o stash
stash-macapp: flags-release flags-pie stash
rm -rf Stash.app
cp -R scripts/macos-bundle Stash.app
mkdir Stash.app/Contents/MacOS
cp stash Stash.app/Contents/MacOS/stash
# build-release-static, but excluding netgo, which is not needed on windows
.PHONY: build-release-static-windows
build-release-static-windows: stash-release-static-windows phasher-release-static-windows
# build-cc- targets should be run within the compiler docker container
# cross-compile- targets should be run within the compiler docker container
.PHONY: cross-compile-windows
cross-compile-windows: export GOOS := windows
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: STASH_OUTPUT := -o dist/stash-win.exe
cross-compile-windows: PHASHER_OUTPUT := -o dist/phasher-win.exe
cross-compile-windows: flags-release
cross-compile-windows: flags-static-windows
cross-compile-windows: build
.PHONY: build-cc-windows
build-cc-windows: export GOOS := windows
build-cc-windows: export GOARCH := amd64
build-cc-windows: export CC := x86_64-w64-mingw32-gcc
build-cc-windows: STASH_OUTPUT := -o dist/stash-win.exe
build-cc-windows: PHASHER_OUTPUT :=-o dist/phasher-win.exe
build-cc-windows: flags-release
build-cc-windows: flags-static-windows
build-cc-windows: build
.PHONY: cross-compile-macos-intel
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: STASH_OUTPUT := -o dist/stash-macos-intel
cross-compile-macos-intel: PHASHER_OUTPUT := -o dist/phasher-macos-intel
cross-compile-macos-intel: flags-release
# can't use static build for OSX
cross-compile-macos-intel: flags-pie
cross-compile-macos-intel: build
.PHONY: build-cc-macos-intel
build-cc-macos-intel: export GOOS := darwin
build-cc-macos-intel: export GOARCH := amd64
build-cc-macos-intel: export CC := o64-clang
build-cc-macos-intel: STASH_OUTPUT := -o dist/stash-macos-intel
build-cc-macos-intel: PHASHER_OUTPUT := -o dist/phasher-macos-intel
build-cc-macos-intel: flags-release
# can't use static build for macOS
build-cc-macos-intel: flags-pie
build-cc-macos-intel: build
.PHONY: cross-compile-macos-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: STASH_OUTPUT := -o dist/stash-macos-applesilicon
cross-compile-macos-applesilicon: PHASHER_OUTPUT := -o dist/phasher-macos-applesilicon
cross-compile-macos-applesilicon: flags-release
# can't use static build for OSX
cross-compile-macos-applesilicon: flags-pie
cross-compile-macos-applesilicon: build
.PHONY: build-cc-macos-arm
build-cc-macos-arm: export GOOS := darwin
build-cc-macos-arm: export GOARCH := arm64
build-cc-macos-arm: export CC := oa64e-clang
build-cc-macos-arm: STASH_OUTPUT := -o dist/stash-macos-arm
build-cc-macos-arm: PHASHER_OUTPUT := -o dist/phasher-macos-arm
build-cc-macos-arm: flags-release
# can't use static build for macOS
build-cc-macos-arm: flags-pie
build-cc-macos-arm: build
.PHONY: build-cc-macos
build-cc-macos:
make build-cc-macos-arm
make build-cc-macos-intel
# Combine into universal binaries
lipo -create -output dist/stash-macos dist/stash-macos-intel dist/stash-macos-arm
rm dist/stash-macos-intel dist/stash-macos-arm
lipo -create -output dist/phasher-macos dist/phasher-macos-intel dist/phasher-macos-arm
rm dist/phasher-macos-intel dist/phasher-macos-arm
.PHONY: cross-compile-macos
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
rm -rf dist/Stash.app
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 ..
cp dist/stash-macos dist/Stash.app/Contents/MacOS/stash
cd dist && rm -f Stash.app.zip && zip -r Stash.app.zip Stash.app
rm -rf dist/Stash.app
.PHONY: cross-compile-freebsd
cross-compile-freebsd: export GOOS := freebsd
cross-compile-freebsd: export GOARCH := amd64
cross-compile-freebsd: STASH_OUTPUT := -o dist/stash-freebsd
cross-compile-freebsd: PHASHER_OUTPUT := -o dist/phasher-freebsd
cross-compile-freebsd: flags-release
cross-compile-freebsd: flags-static-pie
cross-compile-freebsd: build
.PHONY: build-cc-freebsd
build-cc-freebsd: export GOOS := freebsd
build-cc-freebsd: export GOARCH := amd64
build-cc-freebsd: export CC := clang -target x86_64-unknown-freebsd12.0 --sysroot=/opt/cross-freebsd
build-cc-freebsd: STASH_OUTPUT := -o dist/stash-freebsd
build-cc-freebsd: PHASHER_OUTPUT := -o dist/phasher-freebsd
build-cc-freebsd: flags-release
build-cc-freebsd: flags-static-pie
build-cc-freebsd: build
.PHONY: cross-compile-linux
cross-compile-linux: export GOOS := linux
cross-compile-linux: export GOARCH := amd64
cross-compile-linux: STASH_OUTPUT := -o dist/stash-linux
cross-compile-linux: PHASHER_OUTPUT := -o dist/phasher-linux
cross-compile-linux: flags-release
cross-compile-linux: flags-static-pie
cross-compile-linux: build
.PHONY: build-cc-linux
build-cc-linux: export GOOS := linux
build-cc-linux: export GOARCH := amd64
build-cc-linux: STASH_OUTPUT := -o dist/stash-linux
build-cc-linux: PHASHER_OUTPUT := -o dist/phasher-linux
build-cc-linux: flags-release
build-cc-linux: flags-static-pie
build-cc-linux: build
.PHONY: cross-compile-linux-arm64v8
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: STASH_OUTPUT := -o dist/stash-linux-arm64v8
cross-compile-linux-arm64v8: PHASHER_OUTPUT := -o dist/phasher-linux-arm64v8
cross-compile-linux-arm64v8: flags-release
cross-compile-linux-arm64v8: flags-static-pie
cross-compile-linux-arm64v8: build
.PHONY: build-cc-linux-arm64v8
build-cc-linux-arm64v8: export GOOS := linux
build-cc-linux-arm64v8: export GOARCH := arm64
build-cc-linux-arm64v8: export CC := aarch64-linux-gnu-gcc
build-cc-linux-arm64v8: STASH_OUTPUT := -o dist/stash-linux-arm64v8
build-cc-linux-arm64v8: PHASHER_OUTPUT := -o dist/phasher-linux-arm64v8
build-cc-linux-arm64v8: flags-release
build-cc-linux-arm64v8: flags-static-pie
build-cc-linux-arm64v8: build
.PHONY: cross-compile-linux-arm32v7
cross-compile-linux-arm32v7: export GOOS := linux
cross-compile-linux-arm32v7: export GOARCH := arm
cross-compile-linux-arm32v7: export GOARM := 7
cross-compile-linux-arm32v7: export CC := arm-linux-gnueabi-gcc -march=armv7-a
cross-compile-linux-arm32v7: STASH_OUTPUT := -o dist/stash-linux-arm32v7
cross-compile-linux-arm32v7: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v7
cross-compile-linux-arm32v7: flags-release
cross-compile-linux-arm32v7: flags-static
cross-compile-linux-arm32v7: build
.PHONY: build-cc-linux-arm32v7
build-cc-linux-arm32v7: export GOOS := linux
build-cc-linux-arm32v7: export GOARCH := arm
build-cc-linux-arm32v7: export GOARM := 7
build-cc-linux-arm32v7: export CC := arm-linux-gnueabi-gcc -march=armv7-a
build-cc-linux-arm32v7: STASH_OUTPUT := -o dist/stash-linux-arm32v7
build-cc-linux-arm32v7: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v7
build-cc-linux-arm32v7: flags-release
build-cc-linux-arm32v7: flags-static
build-cc-linux-arm32v7: build
.PHONY: cross-compile-linux-arm32v6
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: STASH_OUTPUT := -o dist/stash-linux-arm32v6
cross-compile-linux-arm32v6: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v6
cross-compile-linux-arm32v6: flags-release
cross-compile-linux-arm32v6: flags-static
cross-compile-linux-arm32v6: build
.PHONY: build-cc-linux-arm32v6
build-cc-linux-arm32v6: export GOOS := linux
build-cc-linux-arm32v6: export GOARCH := arm
build-cc-linux-arm32v6: export GOARM := 6
build-cc-linux-arm32v6: export CC := arm-linux-gnueabi-gcc
build-cc-linux-arm32v6: STASH_OUTPUT := -o dist/stash-linux-arm32v6
build-cc-linux-arm32v6: PHASHER_OUTPUT := -o dist/phasher-linux-arm32v6
build-cc-linux-arm32v6: flags-release
build-cc-linux-arm32v6: flags-static
build-cc-linux-arm32v6: build
.PHONY: cross-compile-all
cross-compile-all:
make cross-compile-windows
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-linux-arm32v6
.PHONY: build-cc-all
build-cc-all:
make build-cc-windows
make build-cc-macos
make build-cc-linux
make build-cc-linux-arm64v8
make build-cc-linux-arm32v7
make build-cc-linux-arm32v6
make build-cc-freebsd
.PHONY: touch-ui
touch-ui:
@@ -282,12 +275,16 @@ generate: generate-backend generate-ui
.PHONY: generate-ui
generate-ui:
cd ui/v2.5 && yarn run gqlgen
cd ui/v2.5 && npm run gqlgen
.PHONY: generate-backend
generate-backend: touch-ui
go generate ./cmd/stash
.PHONY: generate-login-locale
generate-login-locale:
go generate ./ui
.PHONY: generate-dataloaders
generate-dataloaders:
go generate ./internal/api/loaders
@@ -314,7 +311,8 @@ test:
# runs all tests - including integration tests
.PHONY: it
it:
go test -tags=integration ./...
$(eval GO_BUILD_TAGS += integration)
go test -tags "$(GO_BUILD_TAGS)" ./...
# generates test mocks
.PHONY: generate-test-mocks
@@ -340,9 +338,19 @@ server-clean:
# installs UI dependencies. Run when first cloning repository, or if UI
# dependencies have changed
# If CI is set, configures pnpm to use a local store to avoid
# putting .pnpm-store in /stash
# NOTE: to run in the docker build container, using the existing
# node_modules folder, rename the .modules.yaml to .modules.yaml.bak
# and a new one will be generated. This will need to be reversed after
# building.
.PHONY: pre-ui
pre-ui:
cd ui/v2.5 && yarn install --frozen-lockfile
ifdef CI
cd ui/v2.5 && pnpm config set store-dir ~/.pnpm-store && pnpm install --frozen-lockfile
else
cd ui/v2.5 && pnpm install --frozen-lockfile
endif
.PHONY: ui-env
ui-env: build-info
@@ -357,29 +365,47 @@ ifdef STASH_SOURCEMAPS
endif
.PHONY: ui
ui: ui-env
cd ui/v2.5 && yarn build
ui: ui-only generate-login-locale
.PHONY: ui-nolegacy
ui-nolegacy: STASH_NOLEGACY := true
ui-nolegacy: ui
.PHONY: ui-only
ui-only: ui-env
cd ui/v2.5 && npm run build
.PHONY: ui-sourcemaps
ui-sourcemaps: STASH_SOURCEMAPS := true
ui-sourcemaps: ui
.PHONY: zip-ui
zip-ui:
rm -f dist/stash-ui.zip
cd ui/v2.5/build && zip -r ../../../dist/stash-ui.zip .
.PHONY: ui-start
ui-start: ui-env
cd ui/v2.5 && yarn start --host
cd ui/v2.5 && npm run start -- --host
.PHONY: fmt-ui
fmt-ui:
cd ui/v2.5 && yarn format
cd ui/v2.5 && npm run format
# runs all of the frontend PR-acceptance steps
.PHONY: validate-ui
validate-ui:
cd ui/v2.5 && yarn run validate
cd ui/v2.5 && npm run validate
# these targets run the same steps as fmt-ui and validate-ui, but only on files that have changed
fmt-ui-quick:
cd ui/v2.5 && \
files=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
if [ -n "$$files" ]; then \
npm run prettier -- --write $$files; \
fi
# does not run tsc checks, as they are slow
validate-ui-quick:
cd ui/v2.5 && \
tsfiles=$$(git diff --name-only --relative --diff-filter d src | grep -e "\.tsx\?\$$"); \
scssfiles=$$(git diff --name-only --relative --diff-filter d src | grep "\.scss"); \
prettyfiles=$$(git diff --name-only --relative --diff-filter d . ../../graphql); \
if [ -n "$$tsfiles" ]; then npm run eslint -- $$tsfiles; fi && \
if [ -n "$$scssfiles" ]; then npm run stylelint -- $$scssfiles; fi && \
if [ -n "$$prettyfiles" ]; then npm run prettier -- --check $$prettyfiles; fi
# runs all of the backend PR-acceptance steps
.PHONY: validate-backend
@@ -398,3 +424,16 @@ docker-build: build-info
.PHONY: docker-cuda-build
docker-cuda-build: build-info
docker build --build-arg GITHASH=$(GITHASH) --build-arg STASH_VERSION=$(STASH_VERSION) -t stash/cuda-build -f docker/build/x86_64/Dockerfile-CUDA .
# start the build container - for cross compilation
# this is adapted from the github actions build.yml file
.PHONY: start-compiler-container
start-compiler-container:
docker run -d --name build --mount type=bind,source="$(PWD)",target=/stash,consistency=delegated $(EXTRA_CONTAINER_ARGS) -w /stash $(COMPILER_IMAGE) tail -f /dev/null
# run the cross-compilation using
# docker exec -t build /bin/bash -c "make build-cc-<platform>"
.PHONY: remove-compiler-container
remove-compiler-container:
docker rm -f -v build

View File

@@ -5,13 +5,13 @@
[![GitHub Sponsors](https://img.shields.io/github/sponsors/stashapp?logo=github)](https://github.com/sponsors/stashapp)
[![Open Collective backers](https://img.shields.io/opencollective/backers/stashapp?logo=opencollective)](https://opencollective.com/stashapp)
[![Go Report Card](https://goreportcard.com/badge/github.com/stashapp/stash)](https://goreportcard.com/report/github.com/stashapp/stash)
[![Matrix](https://img.shields.io/matrix/stashapp:unredacted.org?logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#stashapp:unredacted.org)
[![Discord](https://img.shields.io/discord/559159668438728723.svg?logo=discord)](https://discord.gg/2TsNFKt)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/stashapp/stash?logo=github)](https://github.com/stashapp/stash/releases/latest)
[![GitHub issues by-label](https://img.shields.io/github/issues-raw/stashapp/stash/bounty)](https://github.com/stashapp/stash/labels/bounty)
### **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 self-hosted webapp written in Go which organizes and serves your diverse content collection, catering to both your SFW and NSFW needs.**
![Screenshot of Stash web application interface](docs/readme_assets/demo_image.png)
* 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.
@@ -20,55 +20,88 @@
You can [watch a SFW demo video](https://vimeo.com/545323354) to see it in action.
For further information you can consult the [documentation](https://docs.stashapp.cc) or [read the in-app manual](ui/v2.5/src/docs/en).
For further information you can consult the [documentation](https://docs.stashapp.cc) or access the in-app manual from within the application (also available at [docs.stashapp.cc/in-app-manual](https://docs.stashapp.cc/in-app-manual)).
# Installing Stash
<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
Step-by-step instructions are available at [docs.stashapp.cc/installation](https://docs.stashapp.cc/installation/).
#### Windows Users:
As of version 0.27.0, Stash no longer supports _Windows 7, 8, Server 2008 and Server 2012._
At least Windows 10 or Server 2016 is required.
#### Mac Users:
As of version 0.29.0, Stash requires _macOS 11 Big Sur_ or later.
Stash can still be run through docker on older versions of macOS.
<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>
[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](https://github.com/stashapp/stash/releases/latest/download/Stash.app.zip) <br /> <sup><sub>[Development Preview](https://github.com/stashapp/stash/releases/download/latest_develop/Stash.app.zip)</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>
Download links for other platforms and architectures are available on the [Releases](https://github.com/stashapp/stash/releases) page.
## 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
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.
#### Windows/macOS Users: Security Prompt
On Windows or macOS, running the app might present a security prompt since the application binary isn't yet signed.
- On Windows, bypass this by clicking "more info" and then the "run anyway" button.
- On macOS, Control+Click the app, click "Open", and then "Open" again.
#### ffmpeg
Stash requires FFmpeg. If you don't have it installed, Stash will prompt you to download a copy during setup. It is recommended that Linux users install `ffmpeg` from their distro's package manager.
# Usage
## Quickstart Guide
Stash is a web-based application. Once the application is running, the interface is available (by default) from http://localhost:9999.
Stash is a web-based application. Once the application is running, the interface is available (by default) from `http://localhost:9999`.
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.
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.
Many community-maintained scrapers are available for download from [CommunityScrapers repository](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 can pull metadata (performers, tags, descriptions, studios, and more) directly from many sites through the use of [scrapers](https://github.com/stashapp/stash/blob/develop/ui/v2.5/src/docs/en/Manual/Scraping.md), which integrate directly into Stash. Identifying an entire collection will typically require a mix of multiple sources:
- The stashapp team maintains [StashDB](https://stashdb.org/), a crowd-sourced repository of scene, studio, and performer information. Connecting it to Stash will allow you to automatically identify much of a typical media collection. It runs on our stash-box software and is primarily focused on mainstream digital scenes and studios. Instructions, invite codes, and more can be found in this guide to [Accessing StashDB](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stashdb/).
- Several community-managed stash-box databases can also be connected to Stash in a similar manner. Each one serves a slightly different niche and follows their own methodology. A rundown of each stash-box, their differences, and the information you need to sign up can be found in this guide to [Accessing Stash-Boxes](https://guidelines.stashdb.org/docs/faq_getting-started/stashdb/accessing-stash-boxes/).
- Many community-maintained scrapers can also be downloaded, installed, and updated from within Stash, allowing you to pull data from a wide range of other websites and databases. They can be found by navigating to `Settings → Metadata Providers → Available Scrapers → Community (stable)`. These can be trickier to use than a stash-box because every scraper works a little differently. For more information, please visit the [CommunityScrapers repository](https://github.com/stashapp/CommunityScrapers).
- All of the above methods of scraping data into Stash are also covered in more detail in our [Guide to Scraping](https://docs.stashapp.cc/beginner-guides/guide-to-scraping/).
<sub>[StashDB](http://stashdb.org) is the canonical instance of our open source metadata API, [stash-box](https://github.com/stashapp/stash-box).</sub>
# Translation
[![Translate](https://hosted.weblate.org/widget/stashapp/stash/svg-badge.svg)](https://hosted.weblate.org/engage/stashapp/)
🇧🇷 🇨🇳 🇩🇰 🇳🇱 🇬🇧 🇪🇪 🇫🇮 🇫🇷 🇩🇪 🇮🇹 🇯🇵 🇰🇷 🇵🇱 🇷🇺 🇪🇸 🇸🇪 🇹🇼 🇹🇷
Stash is available in 25 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash into your language, you can make an account at [Stash's Weblate](https://hosted.weblate.org/projects/stashapp/stash/) to get started contributing new languages or improving existing ones. Thanks!
[![Translate](https://translate.codeberg.org/widget/stash/stash/svg-badge.svg)](https://translate.codeberg.org/engage/stash/)
# Support (FAQ)
Stash is available in 32 languages (so far!) and it could be in your language too. We use Weblate to coordinate community translations. If you want to help us translate Stash, you can make an account at [Codeberg's Weblate](https://translate.codeberg.org/projects/stash/stash/) to contribute to new or existing languages. Thanks!
Check out our documentation on [Stash-Docs](https://docs.stashapp.cc) for information about the software, questions, guides, add-ons and more.
The badge below shows the current translation status of Stash across all supported languages:
For more help you can:
* Check the in-app documentation, in the top right corner of the app (it's also mirrored on [Stash-Docs](https://docs.stashapp.cc/in-app-manual))
* Join the [Matrix space](https://matrix.to/#/#stashapp:unredacted.org)
* Join the [Discord server](https://discord.gg/2TsNFKt), where the community can offer support.
* Start a [discussion on GitHub](https://github.com/stashapp/stash/discussions)
[![Translation status](https://translate.codeberg.org/widget/stash/stash/multi-auto.svg)](https://translate.codeberg.org/engage/stash/)
# Customization
# Support & Resources
## Themes and CSS Customization
There is a [directory of community-created themes](https://docs.stashapp.cc/user-interface-ui/themes) on Stash-Docs, along with instructions on how to install them.
Need help or want to get involved? Start with the documentation, then reach out to the community if you need further assistance.
You can also change the Stash interface to fit your desired style with various snippets from [Custom CSS snippets](https://docs.stashapp.cc/user-interface-ui/custom-css-snippets).
- Documentation
- Official docs: https://docs.stashapp.cc - official guides guides and troubleshooting.
- In-app manual: press <kbd>Shift</kbd> + <kbd>?</kbd> in the app or view the manual online: https://docs.stashapp.cc/in-app-manual.
- FAQ: https://discourse.stashapp.cc/c/support/faq/28 - common questions and answers.
- Community wiki: https://discourse.stashapp.cc/tags/c/community-wiki/22/stash - guides, how-tos and tips.
- Community & discussion
- Community forum: https://discourse.stashapp.cc - community support, feature requests and discussions.
- Discord: https://discord.gg/2TsNFKt - real-time chat and community support.
- GitHub discussions: https://github.com/stashapp/stash/discussions - community support and feature discussions.
- Lemmy community: https://discuss.online/c/stashapp - Reddit-style community space.
- Community scrapers & plugins
- Metadata sources: https://docs.stashapp.cc/metadata-sources/
- Plugins: https://docs.stashapp.cc/plugins/
- Themes: https://docs.stashapp.cc/themes/
- Other projects: https://docs.stashapp.cc/other-projects/
# For Developers

View File

@@ -2,9 +2,9 @@
package main
import (
"context"
"fmt"
"os"
"os/exec"
flag "github.com/spf13/pflag"
"github.com/stashapp/stash/pkg/ffmpeg"
@@ -18,7 +18,7 @@ func customUsage() {
flag.PrintDefaults()
}
func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *bool) error {
func printPhash(ff *ffmpeg.FFMpeg, ffp *ffmpeg.FFProbe, inputfile string, quiet *bool) error {
ffvideoFile, err := ffp.NewVideoFile(inputfile)
if err != nil {
return err
@@ -46,6 +46,13 @@ func printPhash(ff *ffmpeg.FFMpeg, ffp ffmpeg.FFProbe, inputfile string, quiet *
return nil
}
func getPaths() (string, string) {
ffmpegPath, _ := exec.LookPath("ffmpeg")
ffprobePath, _ := exec.LookPath("ffprobe")
return ffmpegPath, ffprobePath
}
func main() {
flag.Usage = customUsage
quiet := flag.BoolP("quiet", "q", false, "print only the phash")
@@ -66,14 +73,14 @@ func main() {
}
if len(args) > 1 {
fmt.Fprintln(os.Stderr, "Files will be processed sequentially! Consier using GNU Parallel.")
fmt.Fprintln(os.Stderr, "Files will be processed sequentially! If required, use e.g. GNU Parallel to run concurrently.")
fmt.Fprintf(os.Stderr, "Example: parallel %v ::: *.mp4\n", os.Args[0])
}
ffmpegPath, ffprobePath := ffmpeg.GetPaths(nil)
ffmpegPath, ffprobePath := getPaths()
encoder := ffmpeg.NewEncoder(ffmpegPath)
encoder.InitHWSupport(context.TODO())
ffprobe := ffmpeg.FFProbe(ffprobePath)
// don't need to InitHWSupport, phashing doesn't use hw acceleration
ffprobe := ffmpeg.NewFFProbe(ffprobePath)
for _, item := range args {
if err := printPhash(encoder, ffprobe, item, quiet); err != nil {

View File

@@ -2,67 +2,169 @@
package main
import (
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"runtime/debug"
"runtime/pprof"
"syscall"
"github.com/spf13/pflag"
"github.com/stashapp/stash/internal/api"
"github.com/stashapp/stash/internal/build"
"github.com/stashapp/stash/internal/desktop"
"github.com/stashapp/stash/internal/log"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/ui"
_ "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
var exitCode = 0
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)
defer func() {
if exitCode != 0 {
os.Exit(exitCode)
}
}()
go handleSignals()
desktop.Start(manager.GetInstance(), &ui.FaviconProvider)
defer recoverPanic()
blockForever()
initLogTemp()
helpFlag := false
pflag.BoolVarP(&helpFlag, "help", "h", false, "show this help text and exit")
versionFlag := false
pflag.BoolVarP(&versionFlag, "version", "v", false, "show version number and exit")
cpuProfilePath := ""
pflag.StringVar(&cpuProfilePath, "cpuprofile", "", "write cpu profile to file")
pflag.Parse()
if helpFlag {
pflag.Usage()
return
}
if versionFlag {
fmt.Println(build.VersionString())
return
}
cfg, err := config.Initialize()
if err != nil {
exitError(fmt.Errorf("config initialization error: %w", err))
return
}
l := initLog(cfg)
if cpuProfilePath != "" {
if err := initProfiling(cpuProfilePath); err != nil {
exitError(err)
return
}
defer pprof.StopCPUProfile()
}
mgr, err := manager.Initialize(cfg, l)
if err != nil {
exitError(fmt.Errorf("manager initialization error: %w", err))
return
}
defer mgr.Shutdown()
server, err := api.Initialize()
if err != nil {
exitError(fmt.Errorf("api initialization error: %w", err))
return
}
defer server.Shutdown()
exit := make(chan int)
go func() {
err := server.Start()
if !errors.Is(err, http.ErrServerClosed) {
exitError(fmt.Errorf("http server error: %w", err))
exit <- 1
}
}()
go handleSignals(exit)
desktop.Start(exit, &ui.FaviconProvider)
exitCode = <-exit
}
// initLogTemp initializes a temporary logger for use before the config is loaded.
// Logs only error level message to stderr.
func initLogTemp() *log.Logger {
l := log.NewLogger()
l.Init("", true, "Error", 0)
logger.Logger = l
return l
}
func initLog(cfg *config.Config) *log.Logger {
l := log.NewLogger()
l.Init(cfg.GetLogFile(), cfg.GetLogOut(), cfg.GetLogLevel(), cfg.GetLogFileMaxSize())
logger.Logger = l
return l
}
func initProfiling(path string) error {
f, err := os.Create(path)
if err != nil {
return fmt.Errorf("unable to create CPU profile file: %v", err)
}
if err = pprof.StartCPUProfile(f); err != nil {
return fmt.Errorf("could not start CPU profiling: %v", err)
}
logger.Infof("profiling to %s", path)
return nil
}
func recoverPanic() {
if p := recover(); p != nil {
handleError(fmt.Errorf("Panic: %v", p))
if err := recover(); err != nil {
exitCode = 1
logger.Errorf("panic: %v\n%s", err, debug.Stack())
if desktop.IsDesktop() {
desktop.FatalError(fmt.Errorf("Panic: %v", err))
}
}
}
func handleError(err error) {
func exitError(err error) {
exitCode = 1
logger.Error(err)
// #5784 - log to stdout as well as the logger
// this does mean that it will log twice if the logger is set to stdout
fmt.Println(err)
if desktop.IsDesktop() {
desktop.FatalError(err)
manager.GetInstance().Shutdown(0)
} else {
panic(err)
}
}
func handleSignals() {
func handleSignals(exit chan<- int) {
// handle signals
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
<-signals
manager.GetInstance().Shutdown(0)
}
func blockForever() {
select {}
exit <- 0
}

View File

@@ -1,34 +1,39 @@
# This dockerfile should be built with `make docker-build` from the stash root.
# Build Frontend
FROM node:alpine as frontend
FROM node:24-alpine AS frontend
RUN apk add --no-cache make git
## cache node_modules separately
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
WORKDIR /stash
COPY Makefile /stash/
COPY ./graphql /stash/graphql/
COPY ./ui /stash/ui/
# pnpm install with npm
RUN npm install -g pnpm
RUN make pre-ui
RUN make generate-ui
ARG GITHASH
ARG STASH_VERSION
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
# Build Backend
FROM golang:1.19-alpine as backend
FROM golang:1.24.3-alpine AS backend
RUN apk add --no-cache make alpine-sdk
WORKDIR /stash
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
COPY ./graphql /stash/graphql/
COPY ./scripts /stash/scripts/
COPY ./pkg /stash/pkg/
COPY ./cmd /stash/cmd
COPY ./internal /stash/internal
COPY ./cmd /stash/cmd/
COPY ./internal /stash/internal/
# needed for generate-login-locale
COPY ./ui /stash/ui/
RUN make generate-backend generate-login-locale
COPY --from=frontend /stash /stash/
RUN make generate-backend
ARG GITHASH
ARG STASH_VERSION
RUN make stash-release
RUN make flags-release flags-pie stash
# Final Runnable Image
FROM alpine:latest

View File

@@ -1,50 +1,65 @@
# This dockerfile should be built with `make docker-cuda-build` from the stash root.
ARG CUDA_VERSION=12.8.0
# Build Frontend
FROM node:alpine as frontend
FROM node:20-alpine AS frontend
RUN apk add --no-cache make git
## cache node_modules separately
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
COPY ./ui/v2.5/package.json ./ui/v2.5/pnpm-lock.yaml /stash/ui/v2.5/
WORKDIR /stash
COPY Makefile /stash/
COPY ./graphql /stash/graphql/
COPY ./ui /stash/ui/
# pnpm install with npm
RUN npm install -g pnpm
RUN make pre-ui
RUN make generate-ui
ARG GITHASH
ARG STASH_VERSION
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui
RUN BUILD_DATE=$(date +"%Y-%m-%d %H:%M:%S") make ui-only
# Build Backend
FROM golang:1.19-bullseye as backend
FROM golang:1.24.3-bullseye AS backend
RUN apt update && apt install -y build-essential golang
WORKDIR /stash
COPY ./go* ./*.go Makefile gqlgen.yml .gqlgenc.yml /stash/
COPY ./graphql /stash/graphql/
COPY ./scripts /stash/scripts/
COPY ./pkg /stash/pkg/
COPY ./cmd /stash/cmd
COPY ./internal /stash/internal
# needed for generate-login-locale
COPY ./ui /stash/ui/
RUN make generate-backend generate-login-locale
COPY --from=frontend /stash /stash/
RUN make generate-backend
ARG GITHASH
ARG STASH_VERSION
RUN make stash-release
RUN make flags-release flags-pie stash
# Final Runnable Image
FROM nvidia/cuda:12.0.1-base-ubuntu22.04
RUN apt update && apt upgrade -y && apt install -y ca-certificates libvips-tools ffmpeg wget intel-media-va-driver-non-free vainfo
RUN rm -rf /var/lib/apt/lists/*
COPY --from=backend /stash/stash /usr/bin/
FROM nvidia/cuda:${CUDA_VERSION}-base-ubuntu24.04
RUN apt update && apt upgrade -y && apt install -y \
# stash dependencies
ca-certificates libvips-tools ffmpeg \
# intel dependencies
intel-media-va-driver-non-free vainfo \
# python tools
python3 python3-pip && \
# cleanup
apt autoremove -y && apt clean && \
rm -rf /var/lib/apt/lists/*
COPY --from=backend --chmod=555 /stash/stash /usr/bin/
# NVENC Patch
RUN mkdir -p /usr/local/bin /patched-lib
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh -O /usr/local/bin/patch.sh
RUN wget https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh -O /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/patch.sh /usr/local/bin/docker-entrypoint.sh /usr/bin/stash
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/patch.sh /usr/local/bin/patch.sh
ADD --chmod=555 https://raw.githubusercontent.com/keylase/nvidia-patch/master/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
ENV LANG C.UTF-8
ENV NVIDIA_VISIBLE_DEVICES all
ENV LANG=C.UTF-8
ENV NVIDIA_VISIBLE_DEVICES=all
ENV NVIDIA_DRIVER_CAPABILITIES=video,utility
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
EXPOSE 9999
ENTRYPOINT ["docker-entrypoint.sh", "stash"]
# vim: ft=dockerfile

View File

@@ -4,7 +4,7 @@ This dockerfile is used to build a stash docker container using the current sour
# Building the docker container
From the top-level directory (should contain `main.go` file):
From the top-level directory (should contain `tools.go` file):
```
make docker-build

View File

@@ -11,12 +11,18 @@ RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then BIN=stash-linux-arm32v6; \
FROM --platform=$TARGETPLATFORM alpine:latest AS app
COPY --from=binary /stash /usr/bin/
RUN apk add --no-cache --virtual .build-deps gcc python3-dev musl-dev \
&& apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg vips-tools ruby tzdata \
&& pip install mechanicalsoup cloudscraper bencoder.pyx \
&& gem install faraday \
&& apk del .build-deps
RUN apk add --no-cache ca-certificates python3 py3-requests py3-requests-toolbelt py3-lxml py3-pip ffmpeg tzdata vips vips-tools \
&& pip install --break-system-packages mechanicalsoup cloudscraper stashapp-tools
ENV STASH_CONFIG_FILE=/root/.stash/config.yml
# Basic build-time metadata as defined at https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
LABEL org.opencontainers.image.title="Stash" \
org.opencontainers.image.description="An organizer for your porn, written in Go." \
org.opencontainers.image.url="https://stashapp.cc" \
org.opencontainers.image.documentation="https://docs.stashapp.cc" \
org.opencontainers.image.source="https://github.com/stashapp/stash" \
org.opencontainers.image.licenses="AGPL-3.0"
EXPOSE 9999
CMD ["stash"]

View File

@@ -1 +1 @@
This dockerfile is used by travis to build the stash image. It must be run after cross-compiling - that is, `stash-linux` must exist in the `dist` directory. This image must be built from the `dist` directory.
This Dockerfile is used by CI to build the `stashapp/stash` Docker image. It must be run after cross-compiling - that is, `stash-linux` must exist in the `dist` directory. This image must be built from the `dist` directory.

View File

@@ -1,67 +1,82 @@
FROM golang:1.19
FROM golang:1.24.3
LABEL maintainer="https://discord.gg/2TsNFKt"
# Install tools
RUN apt-get update && apt-get install -y apt-transport-https
RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg
# prevent caching of the key
ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg
RUN cat yarn.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
rm yarn.gpg
RUN mkdir -p /etc/apt/keyrings
ADD https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key nodesource.gpg.key
RUN cat nodesource.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && rm nodesource.gpg.key
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && \
apt-get install -y automake autogen cmake \
libtool libxml2-dev uuid-dev libssl-dev bash \
patch make tar xz-utils bzip2 gzip zlib1g-dev sed cpio \
gcc-10-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \
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 zip --no-install-recommends || exit 1; \
rm -rf /var/lib/apt/lists/*;
apt-get install -y --no-install-recommends \
git make tar bash nodejs zip \
clang llvm-dev cmake patch libxml2-dev uuid-dev libssl-dev xz-utils \
bzip2 gzip sed cpio libbz2-dev zlib1g-dev \
gcc-mingw-w64 \
gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \
gcc-aarch64-linux-gnu libc-dev-arm64-cross && \
rm -rf /var/lib/apt/lists/*;
# Cross compile setup
ENV OSX_SDK_VERSION 11.3
ENV OSX_SDK_DOWNLOAD_FILE=MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
ENV OSX_SDK_DOWNLOAD_URL=https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
ENV OSX_SDK_SHA=cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4
ENV OSX_SDK MacOSX$OSX_SDK_VERSION.sdk
ENV OSX_NDK_X86 /usr/local/osx-ndk-x86
# pnpm install with npm
RUN npm install -g pnpm
RUN wget ${OSX_SDK_DOWNLOAD_URL}
RUN echo "$OSX_SDK_SHA $OSX_SDK_DOWNLOAD_FILE" | sha256sum -c - || exit 1; \
git clone https://github.com/tpoechtrager/osxcross.git; \
mv $OSX_SDK_DOWNLOAD_FILE osxcross/tarballs/
# FreeBSD cross-compilation setup
# https://github.com/smartmontools/docker-build/blob/6b8c92560d17d325310ba02d9f5a4b250cb0764a/Dockerfile#L66
ENV FREEBSD_VERSION 13.4
ENV FREEBSD_DOWNLOAD_URL http://ftp.plusline.de/FreeBSD/releases/amd64/${FREEBSD_VERSION}-RELEASE/base.txz
ENV FREEBSD_SHA 8e13b0a93daba349b8d28ad246d7beb327659b2ef4fe44d89f447392daec5a7c
RUN UNATTENDED=yes SDK_VERSION=${OSX_SDK_VERSION} OSX_VERSION_MIN=10.10 osxcross/build.sh || exit 1;
RUN cp osxcross/target/lib/* /usr/lib/ ; \
mv osxcross/target $OSX_NDK_X86; \
rm -rf osxcross;
RUN cd /tmp && \
curl -o base.txz $FREEBSD_DOWNLOAD_URL && \
echo "$FREEBSD_SHA base.txz" | sha256sum -c - && \
mkdir -p /opt/cross-freebsd && \
cd /opt/cross-freebsd && \
tar -xf /tmp/base.txz ./lib/ ./usr/lib/ ./usr/include/ && \
rm -f /tmp/base.txz && \
cd /opt/cross-freebsd/usr/lib && \
find . -xtype l | xargs ls -l | grep ' /lib/' | awk '{print "ln -sf /opt/cross-freebsd"$11 " " $9}' | /bin/sh && \
ln -s libc++.a libstdc++.a && \
ln -s libc++.so libstdc++.so
ENV PATH $OSX_NDK_X86/bin:$PATH
# macOS cross-compilation setup
ENV OSX_SDK_VERSION 11.3
ENV OSX_SDK_DOWNLOAD_FILE MacOSX${OSX_SDK_VERSION}.sdk.tar.xz
ENV OSX_SDK_DOWNLOAD_URL https://github.com/phracker/MacOSX-SDKs/releases/download/${OSX_SDK_VERSION}/${OSX_SDK_DOWNLOAD_FILE}
ENV OSX_SDK_SHA cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4
ENV OSXCROSS_REVISION 5e1b71fcceb23952f3229995edca1b6231525b5b
ENV OSXCROSS_DOWNLOAD_URL https://codeload.github.com/tpoechtrager/osxcross/tar.gz/${OSXCROSS_REVISION}
ENV OSXCROSS_SHA d3f771bbc20612fea577b18a71be3af2eb5ad2dd44624196cf55de866d008647
RUN mkdir -p /root/.ssh; \
chmod 0700 /root/.ssh; \
ssh-keyscan github.com > /root/.ssh/known_hosts;
RUN cd /tmp && \
curl -o osxcross.tar.gz $OSXCROSS_DOWNLOAD_URL && \
echo "$OSXCROSS_SHA osxcross.tar.gz" | sha256sum -c - && \
mkdir osxcross && \
tar --strip=1 -C osxcross -xf osxcross.tar.gz && \
rm -f osxcross.tar.gz && \
curl -Lo $OSX_SDK_DOWNLOAD_FILE $OSX_SDK_DOWNLOAD_URL && \
echo "$OSX_SDK_SHA $OSX_SDK_DOWNLOAD_FILE" | sha256sum -c - && \
mv $OSX_SDK_DOWNLOAD_FILE osxcross/tarballs/ && \
UNATTENDED=yes SDK_VERSION=$OSX_SDK_VERSION OSX_VERSION_MIN=10.10 osxcross/build.sh && \
cp osxcross/target/lib/* /usr/lib/ && \
mv osxcross/target /opt/osx-ndk-x86 && \
rm -rf /tmp/osxcross
# Notes for self:
ENV PATH /opt/osx-ndk-x86/bin:$PATH
RUN mkdir -p /root/.ssh && \
chmod 0700 /root/.ssh && \
ssh-keyscan github.com > /root/.ssh/known_hosts
# ignore "dubious ownership" errors
RUN git config --global safe.directory '*'
# 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
# docker run --rm -v /PATH_TO_STASH:/stash -w /stash -i -t stashapp/compiler:latest make build-cc-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=7
version=12
latest:
docker build -t ${user}/${repo}:latest .

View File

@@ -1,3 +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 the github workflow files need to be updated to pull the correct image tag.
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 GitHub workflow files also need to be updated to pull the correct image tag.

View File

@@ -1,25 +1,27 @@
# Docker Installation (for most 64-bit GNU/Linux systems)
StashApp is supported on most systems that support Docker and docker-compose. Your OS likely ships with or makes available the necessary packages.
StashApp is supported on most systems that support Docker. Your OS likely ships with or makes available the necessary packages.
## Dependencies
Only `docker` and `docker-compose` are required. For the most part your understanding of the technologies can be superficial. So long as you can follow commands and are open to reading a bit, you should be fine.
Only `docker` is required. For the most part your understanding of the technologies can be superficial. So long as you can follow commands and are open to reading a bit, you should be fine.
Installation instructions are available below, and if your distrobution's repository ships a current version of docker, you may use that.
Installation instructions are available below, and if your distributions's repository ships a current version of docker, you may use that.
https://docs.docker.com/engine/install/
On some distributions, `docker compose` is shipped seperately, usually as `docker-cli-compose`. docker-compose is not 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:
Now you can either navigate to the [docker-compose.yml](https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml) in the repository, or if you have curl, you can make your Linux console do it for you:
```
mkdir stashapp && cd stashapp
curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/master/docker/production/docker-compose.yml
curl -o docker-compose.yml https://raw.githubusercontent.com/stashapp/stash/develop/docker/production/docker-compose.yml
```
Once you have that file where you want it, modify the settings as you please, and then run:
```
docker-compose up -d
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
@@ -29,9 +31,9 @@ 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.
The StashApp docker container ships with everything you need to automatically 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.
### 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](https://docs.stashapp.cc/guides/reverse-proxy/) (such as NGINX or Traefik) is recommended, but not required.
The latest version is always recommended.

View File

@@ -1,6 +1,5 @@
# APPNICENAME=Stash
# APPDESCRIPTION=An organizer for your porn, written in Go
version: '3.4'
services:
stash:
image: stashapp/stash:latest
@@ -27,10 +26,12 @@ services:
- /etc/localtime:/etc/localtime:ro
## Adjust below paths (the left part) to your liking.
## E.g. you can change ./config:/root/.stash to ./stash:/root/.stash
## The left part is the path on your host, the right part is the path in the stash container.
## Keep configs, scrapers, and plugins here.
- ./config:/root/.stash
## Point this at your collection.
## The left side is where your collection is on your host, the right side is where it will be in stash.
- ./data:/data
## This is where your stash's metadata lives
- ./metadata:/metadata

View File

@@ -1,3 +1,24 @@
## Goals and design vision
The goal of stash is to be:
- an application for organising and viewing adult content - currently this is videos and images, in future this will be extended to include audio and text content
- organising includes scraping of metadata from websites and metadata repositories
- free and open-source
- portable and offline - can be run on a USB stick without needing to install dependencies (with the exception of ffmpeg)
- minimal, but highly extensible. The core feature set should be the minimum required to achieve the primary goal, while being extensible enough to extend via plugins
- easy to learn and use, with minimal technical knowledge required
The core stash system is not intended for:
- managing downloading of content
- managing content on external websites
- publically sharing content
Other requirements:
- support as many video and image formats as possible
- interfaces with external systems (for example stash-box) should be made as generic as possible.
Design considerations:
- features are easy to add and difficult to remove. Large superfluous features should be scrutinised and avoided where possible (eg DLNA, filename parser). Such features should be considered for third-party plugins instead.
## Technical Debt
Please be sure to consider how heavily your contribution impacts the maintainability of the project long term, sometimes less is more. We don't want to merge collossal pull requests with hundreds of dependencies by a driveby contributor.

View File

@@ -4,8 +4,9 @@
* [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
* To install, follow the [local installation instructions](https://golangci-lint.run/welcome/install/#local-installation)
* [nodejs](https://nodejs.org/en/download) - nodejs runtime
* corepack/[pnpm](https://pnpm.io/installation) - nodejs package manager (included with nodejs)
## Environment
@@ -22,19 +23,24 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MinGW. For examp
### 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 node ffmpeg`
2. Install dependencies: `brew install go git gcc make node ffmpeg`
### Linux
#### Arch Linux
1. Install dependencies: `sudo pacman -S go git yarn gcc make nodejs ffmpeg --needed`
1. Install dependencies: `sudo pacman -S go git gcc make nodejs ffmpeg --needed`
#### Ubuntu
1. Install dependencies: `sudo apt-get install golang git gcc nodejs ffmpeg -y`
2. Enable corepack in Node.js: `corepack enable`
3. Install yarn: `corepack prepare yarn@stable --activate`
### OpenBSD
1. Install dependencies `doas pkg_add gmake go git node cmake ffmpeg`
2. Follow the instructions below to build a release, but replace the final step `make build-release` with `gmake flags-release stash`, to [avoid the PIE buildmode](https://github.com/golang/go/issues/59866).
NOTE: The `make` command in OpenBSD will be `gmake`. For example, `make pre-ui` will be `gmake pre-ui`.
## Commands
@@ -43,11 +49,10 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MinGW. For examp
* `make generate-stash-box-client` - Generate Go files for the Stash-box client code.
* `make ui` - Builds the UI. Requires `make pre-ui` to have been run.
* `make stash` - Builds the `stash` binary (make sure to build the UI as well... see below)
* `make stash-release` - Builds a release version the `stash` binary, with debug information removed
* `make stash-macapp` - Builds the `Stash.app` macOS app (only works when on macOS, for cross-compilation see below)
* `make phasher` - Builds the `phasher` binary
* `make phasher-release` - Builds a release version the `phasher` binary, with debug information removed
* `make build` - Builds both the `stash` and `phasher` binaries
* `make build-release` - Builds release versions of both the `stash` and `phasher` binaries
* `make build` - Builds both the `stash` and `phasher` binaries, alias for `make stash phasher`
* `make build-release` - Builds release versions (debug information removed) of both the `stash` and `phasher` binaries, alias for `make flags-release flags-pie build`
* `make docker-build` - Locally builds and tags a complete 'stash/build' docker image
* `make docker-cuda-build` - Locally builds and tags a complete 'stash/cuda-build' docker image
* `make validate` - Runs all of the tests and checks required to submit a PR
@@ -55,9 +60,20 @@ NOTE: The `make` command in Windows will be `mingw32-make` with MinGW. For examp
* `make it` - Runs all unit and integration tests
* `make fmt` - Formats the Go source code
* `make fmt-ui` - Formats the UI source code
* `make validate-ui` - Runs tests and checks for the UI only
* `make fmt-ui-quick` - (experimental) Formats only changed UI source code
* `make validate-ui-quick` - (experimental) Runs tests and checks of changed UI code
* `make server-start` - Runs a development stash server in the `.local` directory
* `make server-clean` - Removes the `.local` directory and all of its contents
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to. The server port can be changed from the default of `9999` using the environment variable `VITE_APP_PLATFORM_PORT`. The UI runs on port `3000` or the next available port.
* `make ui-start` - Runs the UI in development mode. Requires a running Stash server to connect to - the server URL can be changed from the default of `http://localhost:9999` using the environment variable `VITE_APP_PLATFORM_URL`, but keep in mind that authentication cannot be used since the session authorization cookie cannot be sent cross-origin. The UI runs on port `3000` or the next available port.
When building, you can optionally prepend `flags-*` targets to the target list in your `make` command to use different build flags:
* `flags-release` (e.g. `make flags-release stash`) - Remove debug information from the binary.
* `flags-pie` (e.g. `make flags-pie build`) - Build a PIE (Position Independent Executable) binary. This provides increased security, but it is unsupported on some systems (notably 32-bit ARM and OpenBSD).
* `flags-static` (e.g. `make flags-static phasher`) - Build a statically linked binary (the default is a dynamically linked binary).
* `flags-static-pie` (e.g. `make flags-static-pie stash`) - Build a statically linked PIE binary (using `flags-static` and `flags-pie` separately will not work).
* `flags-static-windows` (e.g. `make flags-static-windows build`) - Identical to `flags-static-pie`, but does not enable the `netgo` build tag, which is not needed for static builds on Windows.
## Local development quickstart
@@ -95,13 +111,19 @@ Simply run `make` or `make release`, or equivalently:
3. Run `make ui` to build the frontend
4. Run `make build-release` to build a release executable for your current platform
## Cross compiling
## 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:
This project uses a modification of the [CI-GoReleaser](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) Docker container for cross-compilation, defined in `docker/compiler/Dockerfile`.
`docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -i -t stashapp/compiler:latest /bin/bash`
To cross-compile the app yourself:
1. Run `make pre-ui`, `make generate` and `make ui` outside the container, to generate files and build the UI.
2. Pull the latest compiler image from Docker Hub: `docker pull stashapp/compiler`
3. Run `docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash -it stashapp/compiler /bin/bash` to open a shell inside the container.
4. From inside the container, run `make build-cc-all` to build for all platforms, or run `make build-cc-{platform}` to build for a specific platform (have a look at the `Makefile` for the list of targets).
5. You will find the compiled binaries in `dist/`.
NOTE: Since the container is run as UID 0 (root), the resulting binaries (and the `dist/` folder itself, if it had to be created) will be owned by root.
## Profiling

166
go.mod
View File

@@ -1,115 +1,129 @@
module github.com/stashapp/stash
go 1.24.3
require (
github.com/99designs/gqlgen v0.17.2
github.com/Yamashou/gqlgenc v0.0.6
github.com/99designs/gqlgen v0.17.73
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
github.com/Yamashou/gqlgenc v0.32.1
github.com/anacrolix/dms v1.2.2
github.com/antchfx/htmlquery v1.3.0
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84
github.com/chromedp/chromedp v0.7.3
github.com/corona10/goimagehash v1.0.3
github.com/disintegration/imaging v1.6.0
github.com/go-chi/chi v4.0.2+incompatible
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/golang-migrate/migrate/v4 v4.15.0-beta.1
github.com/asticode/go-astisub v0.25.1
github.com/chromedp/cdproto v0.0.0-20231007061347-18b01cd81617
github.com/chromedp/chromedp v0.9.2
github.com/corona10/goimagehash v1.1.0
github.com/disintegration/imaging v1.6.2
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
github.com/doug-martin/goqu/v9 v9.18.0
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/cors v1.2.1
github.com/go-chi/httplog v0.3.1
github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/golang-migrate/migrate/v4 v4.16.2
github.com/google/uuid v1.6.0
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.5.0
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/jmoiron/sqlx v1.3.1
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/hasura/go-graphql-client v0.13.1
github.com/jinzhu/copier v0.4.0
github.com/jmoiron/sqlx v1.4.0
github.com/json-iterator/go v1.1.12
github.com/mattn/go-sqlite3 v1.14.7
github.com/kermieisinthehouse/gosx-notifier v0.1.2
github.com/kermieisinthehouse/systray v1.2.4
github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/providers/posflag v1.0.1
github.com/knadh/koanf/v2 v2.2.1
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/mitchellh/mapstructure v1.5.0
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/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f
github.com/sirupsen/logrus 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.1
github.com/tidwall/gjson v1.9.3
github.com/tidwall/pretty v1.2.0 // indirect
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cast v1.6.0
github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0
github.com/tidwall/gjson v1.16.0
github.com/vearutop/statigz v1.4.0
github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser/v2 v2.5.27
github.com/vektra/mockery/v2 v2.10.0
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064
golang.org/x/image v0.5.0
golang.org/x/net v0.7.0
golang.org/x/sys v0.5.0
golang.org/x/term v0.5.0
golang.org/x/text v0.7.0
golang.org/x/tools v0.1.12 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
github.com/zencoder/go-dash/v3 v3.0.2
golang.org/x/crypto v0.45.0
golang.org/x/image v0.18.0
golang.org/x/net v0.47.0
golang.org/x/sys v0.38.0
golang.org/x/term v0.37.0
golang.org/x/text v0.31.0
golang.org/x/time v0.10.0
gopkg.in/guregu/null.v4 v4.0.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/WithoutPants/sortorder v0.0.0-20230616003020-921c9ef69552
github.com/asticode/go-astisub v0.20.0
github.com/doug-martin/goqu/v9 v9.18.0
github.com/go-chi/cors v1.2.1
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.2
github.com/xWTF/chardet v0.0.0-20230208095535-c780f2ac244e
github.com/zencoder/go-dash/v3 v3.0.2
gopkg.in/guregu/null.v4 v4.0.0
)
require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/antchfx/xpath v1.2.3 // 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.1 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi/v5 v5.0.0 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-viper/mapstructure/v2 v2.4.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/gobwas/ws v1.3.0 // indirect
github.com/goccy/go-yaml v1.18.0 // 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/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matryer/moq v0.2.3 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cobra v1.7.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/spf13/viper v1.16.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/urfave/cli/v2 v2.8.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // 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
github.com/tidwall/pretty v1.2.1 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect
golang.org/x/mod v0.29.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/tools v0.38.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
go 1.19

694
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -7,19 +7,17 @@ exec:
filename: internal/api/generated_exec.go
model:
filename: internal/api/generated_models.go
resolver:
filename: internal/api/resolver.go
type: Resolver
struct_tag: gqlgen
autobind:
- github.com/stashapp/stash/internal/api
- 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
- github.com/stashapp/stash/pkg/stashbox
models:
# Scalars
@@ -33,36 +31,24 @@ models:
model: github.com/99designs/gqlgen/graphql.Int64
Timestamp:
model: github.com/stashapp/stash/internal/api.Timestamp
# define to force resolvers
Image:
model: github.com/stashapp/stash/pkg/models.Image
fields:
title:
resolver: true
# override models, from internal/api/models.go
BaseFile:
model: github.com/stashapp/stash/internal/api.BaseFile
GalleryFile:
model: github.com/stashapp/stash/internal/api.GalleryFile
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
BoolMap:
model: github.com/stashapp/stash/internal/api.BoolMap
PluginConfigMap:
model: github.com/stashapp/stash/internal/api.PluginConfigMap
File:
model: github.com/stashapp/stash/internal/api.File
VideoFile:
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
# override float fields - #1572
duration:
fieldName: DurationFinite
frame_rate:
fieldName: FrameRateFinite
ImageFile:
fields:
# override fingerprint field
fingerprints:
fieldName: FingerprintSlice
# movie is group under the hood
Movie:
model: github.com/stashapp/stash/pkg/models.Group
MovieFilterType:
model: github.com/stashapp/stash/pkg/models.GroupFilterType
# autobind on config causes generation issues
BlobsStorageType:
model: github.com/stashapp/stash/internal/manager/config.BlobsStorageType
@@ -82,6 +68,8 @@ models:
model: github.com/stashapp/stash/internal/manager/config.ConfigDisableDropdownCreate
ScanMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.ScanMetadataOptions
CleanGeneratedInput:
model: github.com/stashapp/stash/internal/manager/task.CleanGeneratedOptions
AutoTagMetadataOptions:
model: github.com/stashapp/stash/internal/manager/config.AutoTagMetadataOptions
SystemStatus:
@@ -137,9 +125,6 @@ models:
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:
@@ -150,4 +135,9 @@ models:
model: github.com/stashapp/stash/pkg/scraper.Source
SavedFindFilterType:
model: github.com/stashapp/stash/pkg/models.FindFilterType
# force resolvers
ConfigResult:
fields:
plugins:
resolver: true

View File

@@ -1,6 +0,0 @@
fragment SlimMovieData on Movie {
id
name
front_image_path
rating100
}

View File

@@ -1,25 +0,0 @@
fragment MovieData on Movie {
id
name
aliases
duration
date
rating100
director
studio {
...SlimStudioData
}
synopsis
url
front_image_path
back_image_path
scene_count
scenes {
id
title
path
}
}

View File

@@ -1,25 +0,0 @@
mutation MovieCreate($input: MovieCreateInput!) {
movieCreate(input: $input) {
...MovieData
}
}
mutation MovieUpdate($input: MovieUpdateInput!) {
movieUpdate(input: $input) {
...MovieData
}
}
mutation BulkMovieUpdate($input: BulkMovieUpdateInput!) {
bulkMovieUpdate(input: $input) {
...MovieData
}
}
mutation MovieDestroy($id: ID!) {
movieDestroy(input: { id: $id })
}
mutation MoviesDestroy($ids: [ID!]!) {
moviesDestroy(ids: $ids)
}

View File

@@ -1,11 +0,0 @@
mutation ReloadPlugins {
reloadPlugins
}
mutation RunPluginTask(
$plugin_id: ID!
$task_name: String!
$args: [PluginArgInput!]
) {
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
}

View File

@@ -1,3 +0,0 @@
mutation ReloadScrapers {
reloadScrapers
}

View File

@@ -1,17 +0,0 @@
query FindGalleries(
$filter: FindFilterType
$gallery_filter: GalleryFilterType
) {
findGalleries(gallery_filter: $gallery_filter, filter: $filter) {
count
galleries {
...SlimGalleryData
}
}
}
query FindGallery($id: ID!) {
findGallery(id: $id) {
...GalleryData
}
}

View File

@@ -1,14 +0,0 @@
query FindMovies($filter: FindFilterType, $movie_filter: MovieFilterType) {
findMovies(filter: $filter, movie_filter: $movie_filter) {
count
movies {
...MovieData
}
}
}
query FindMovie($id: ID!) {
findMovie(id: $id) {
...MovieData
}
}

View File

@@ -1,31 +0,0 @@
query Plugins {
plugins {
id
name
description
url
version
tasks {
name
description
}
hooks {
name
description
hooks
}
}
}
query PluginTasks {
pluginTasks {
name
description
plugin {
id
name
}
}
}

View File

@@ -1,3 +0,0 @@
query ScrapeFreeonesPerformers($q: String!) {
scrapeFreeonesPerformerList(query: $q)
}

View File

@@ -4,6 +4,27 @@ type Query {
findSavedFilter(id: ID!): SavedFilter
findSavedFilters(mode: FilterMode): [SavedFilter!]!
findDefaultFilter(mode: FilterMode!): SavedFilter
@deprecated(reason: "default filter now stored in UI config")
"Find a file by its id or path"
findFile(id: ID, path: String): BaseFile!
"Queries for Files"
findFiles(
file_filter: FileFilterType
filter: FindFilterType
ids: [ID!]
): FindFilesResultType!
"Find a file by its id or path"
findFolder(id: ID, path: String): Folder!
"Queries for Files"
findFolders(
folder_filter: FolderFilterType
filter: FindFilterType
ids: [ID!]
): FindFoldersResultType!
"Find a scene by ID or Checksum"
findScene(id: ID, checksum: String): Scene
@@ -12,7 +33,8 @@ type Query {
"A function which queries Scene objects"
findScenes(
scene_filter: SceneFilterType
scene_ids: [Int!]
scene_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
filter: FindFilterType
): FindScenesResultType!
@@ -43,6 +65,7 @@ type Query {
findSceneMarkers(
scene_marker_filter: SceneMarkerFilterType
filter: FindFilterType
ids: [ID!]
): FindSceneMarkersResultType!
findImage(id: ID, checksum: String): Image
@@ -50,7 +73,8 @@ type Query {
"A function which queries Scene objects"
findImages(
image_filter: ImageFilterType
image_ids: [Int!]
image_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
filter: FindFilterType
): FindImagesResultType!
@@ -60,7 +84,8 @@ type Query {
findPerformers(
performer_filter: PerformerFilterType
filter: FindFilterType
performer_ids: [Int!]
performer_ids: [Int!] @deprecated(reason: "use ids")
ids: [ID!]
): FindPerformersResultType!
"Find a studio by ID"
@@ -69,26 +94,39 @@ type Query {
findStudios(
studio_filter: StudioFilterType
filter: FindFilterType
ids: [ID!]
): FindStudiosResultType!
"Find a movie by ID"
findMovie(id: ID!): Movie
findMovie(id: ID!): Movie @deprecated(reason: "Use findGroup instead")
"A function which queries Movie objects"
findMovies(
movie_filter: MovieFilterType
filter: FindFilterType
): FindMoviesResultType!
ids: [ID!]
): FindMoviesResultType! @deprecated(reason: "Use findGroups instead")
"Find a group by ID"
findGroup(id: ID!): Group
"A function which queries Group objects"
findGroups(
group_filter: GroupFilterType
filter: FindFilterType
ids: [ID!]
): FindGroupsResultType!
findGallery(id: ID!): Gallery
findGalleries(
gallery_filter: GalleryFilterType
filter: FindFilterType
ids: [ID!]
): FindGalleriesResultType!
findTag(id: ID!): Tag
findTags(
tag_filter: TagFilterType
filter: FindFilterType
ids: [ID!]
): FindTagsResultType!
"Retrieve random scene markers for the wall"
@@ -109,14 +147,6 @@ type Query {
"List available scrapers"
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(
@@ -135,6 +165,12 @@ type Query {
input: ScrapeSingleStudioInput!
): [ScrapedStudio!]!
"Scrape for a single tag"
scrapeSingleTag(
source: ScraperSourceInput!
input: ScrapeSingleTagInput!
): [ScrapedTag!]!
"Scrape for a single performer"
scrapeSinglePerformer(
source: ScraperSourceInput!
@@ -156,7 +192,19 @@ type Query {
scrapeSingleMovie(
source: ScraperSourceInput!
input: ScrapeSingleMovieInput!
): [ScrapedMovie!]!
): [ScrapedMovie!]! @deprecated(reason: "Use scrapeSingleGroup instead")
"Scrape for a single group"
scrapeSingleGroup(
source: ScraperSourceInput!
input: ScrapeSingleGroupInput!
): [ScrapedGroup!]!
"Scrape for a single image"
scrapeSingleImage(
source: ScraperSourceInput!
input: ScrapeSingleImageInput!
): [ScrapedImage!]!
"Scrapes content based on a URL"
scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent
@@ -167,29 +215,13 @@ type Query {
scrapeSceneURL(url: String!): ScrapedScene
"Scrapes a complete gallery record based on a URL"
scrapeGalleryURL(url: String!): ScrapedGallery
"Scrapes a complete image record based on a URL"
scrapeImageURL(url: String!): ScrapedImage
"Scrapes a complete movie record based on a URL"
scrapeMovieURL(url: String!): ScrapedMovie
"Scrape a list of performers based on name"
scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]!
@deprecated(reason: "use scrapeSinglePerformer")
"Scrapes a complete performer record based on a scrapePerformerList result"
scrapePerformer(
scraper_id: ID!
scraped_performer: ScrapedPerformerInput!
): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer")
"Scrapes a complete scene record based on an existing scene"
scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene
@deprecated(reason: "use scrapeSingleScene")
"Scrapes a complete gallery record based on an existing gallery"
scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery
@deprecated(reason: "use scrapeSingleGallery")
"Scrape a list of performers from a query"
scrapeFreeonesPerformerList(query: String!): [String!]!
@deprecated(
reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones"
)
@deprecated(reason: "Use scrapeGroupURL instead")
"Scrapes a complete group record based on a URL"
scrapeGroupURL(url: String!): ScrapedGroup
# Plugins
"List loaded plugins"
@@ -197,6 +229,12 @@ type Query {
"List available plugin operations"
pluginTasks: [PluginTask!]
# Packages
"List installed packages"
installedPackages(type: PackageType!): [Package!]!
"List available packages"
availablePackages(type: PackageType!, source: String!): [Package!]!
# Config
"Returns the current, complete configuration"
configuration: ConfigResult!
@@ -220,16 +258,16 @@ type Query {
# Get everything
allScenes: [Scene!]!
allScenes: [Scene!]! @deprecated(reason: "Use findScenes instead")
allSceneMarkers: [SceneMarker!]!
allImages: [Image!]!
allGalleries: [Gallery!]!
allStudios: [Studio!]!
allMovies: [Movie!]!
allTags: [Tag!]!
@deprecated(reason: "Use findSceneMarkers instead")
allImages: [Image!]! @deprecated(reason: "Use findImages instead")
allGalleries: [Gallery!]! @deprecated(reason: "Use findGalleries instead")
# @deprecated
allPerformers: [Performer!]!
allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
allMovies: [Movie!]! @deprecated(reason: "Use findGroups instead")
# Get everything with minimal metadata
@@ -242,7 +280,12 @@ type Query {
type Mutation {
setup(input: SetupInput!): Boolean!
migrate(input: MigrateInput!): Boolean!
"Migrates the schema to the required version. Returns the job ID"
migrate(input: MigrateInput!): ID!
"Downloads and installs ffmpeg and ffprobe binaries into the configuration directory. Returns the job ID."
downloadFFMpeg: ID!
sceneCreate(input: SceneCreateInput!): Scene
sceneUpdate(input: SceneUpdateInput!): Scene
@@ -253,24 +296,47 @@ type Mutation {
scenesUpdate(input: [SceneUpdateInput!]!): [Scene]
"Increments the o-counter for a scene. Returns the new value"
sceneIncrementO(id: ID!): Int!
sceneIncrementO(id: ID!): Int! @deprecated(reason: "Use sceneAddO instead")
"Decrements the o-counter for a scene. Returns the new value"
sceneDecrementO(id: ID!): Int!
sceneDecrementO(id: ID!): Int! @deprecated(reason: "Use sceneRemoveO instead")
"Increments the o-counter for a scene. Uses the current time if none provided."
sceneAddO(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Decrements the o-counter for a scene, removing the last recorded time if specific time not provided. Returns the new value"
sceneDeleteO(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"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!
"Resets the resume time point and play duration"
sceneResetActivity(
id: ID!
reset_resume: Boolean
reset_duration: Boolean
): Boolean!
"Increments the play count for the scene. Returns the new play count value."
sceneIncrementPlayCount(id: ID!): Int!
@deprecated(reason: "Use sceneAddPlay instead")
"Increments the play count for the scene. Uses the current time if none provided."
sceneAddPlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Decrements the play count for the scene, removing the specific times or the last recorded time if not provided."
sceneDeletePlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
"Resets the play count for a scene to 0. Returns the new play count value."
sceneResetPlayCount(id: ID!): Int!
"Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"
sceneGenerateScreenshot(id: ID!, at: Float): String!
sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
bulkSceneMarkerUpdate(input: BulkSceneMarkerUpdateInput!): [SceneMarker!]
sceneMarkerDestroy(id: ID!): Boolean!
sceneMarkersDestroy(ids: [ID!]!): Boolean!
sceneAssignFile(input: AssignSceneFileInput!): Boolean!
@@ -295,6 +361,8 @@ type Mutation {
addGalleryImages(input: GalleryAddInput!): Boolean!
removeGalleryImages(input: GalleryRemoveInput!): Boolean!
setGalleryCover(input: GallerySetCoverInput!): Boolean!
resetGalleryCover(input: GalleryResetCoverInput!): Boolean!
galleryChapterCreate(input: GalleryChapterCreateInput!): GalleryChapter
galleryChapterUpdate(input: GalleryChapterUpdateInput!): GalleryChapter
@@ -310,18 +378,37 @@ type Mutation {
studioUpdate(input: StudioUpdateInput!): Studio
studioDestroy(input: StudioDestroyInput!): Boolean!
studiosDestroy(ids: [ID!]!): Boolean!
bulkStudioUpdate(input: BulkStudioUpdateInput!): [Studio!]
movieCreate(input: MovieCreateInput!): Movie
@deprecated(reason: "Use groupCreate instead")
movieUpdate(input: MovieUpdateInput!): Movie
@deprecated(reason: "Use groupUpdate instead")
movieDestroy(input: MovieDestroyInput!): Boolean!
@deprecated(reason: "Use groupDestroy instead")
moviesDestroy(ids: [ID!]!): Boolean!
@deprecated(reason: "Use groupsDestroy instead")
bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
@deprecated(reason: "Use bulkGroupUpdate instead")
groupCreate(input: GroupCreateInput!): Group
groupUpdate(input: GroupUpdateInput!): Group
groupDestroy(input: GroupDestroyInput!): Boolean!
groupsDestroy(ids: [ID!]!): Boolean!
bulkGroupUpdate(input: BulkGroupUpdateInput!): [Group!]
addGroupSubGroups(input: GroupSubGroupAddInput!): Boolean!
removeGroupSubGroups(input: GroupSubGroupRemoveInput!): Boolean!
"Reorder sub groups within a group. Returns true if successful."
reorderSubGroups(input: ReorderSubGroupsInput!): Boolean!
tagCreate(input: TagCreateInput!): Tag
tagUpdate(input: TagUpdateInput!): Tag
tagDestroy(input: TagDestroyInput!): Boolean!
tagsDestroy(ids: [ID!]!): Boolean!
tagsMerge(input: TagsMergeInput!): Tag
bulkTagUpdate(input: BulkTagUpdateInput!): [Tag!]
"""
Moves the given files to the given destination. Returns true if successful.
@@ -335,10 +422,13 @@ type Mutation {
moveFiles(input: MoveFilesInput!): Boolean!
deleteFiles(ids: [ID!]!): Boolean!
fileSetFingerprints(input: FileSetFingerprintsInput!): Boolean!
# Saved filters
saveFilter(input: SaveFilterInput!): SavedFilter!
destroySavedFilter(input: DestroyFilterInput!): Boolean!
setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
@deprecated(reason: "now uses UI config")
"Change general configuration options"
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
@@ -349,9 +439,19 @@ type Mutation {
input: ConfigDefaultSettingsInput!
): ConfigDefaultSettingsResult!
# overwrites the entire UI configuration
configureUI(input: Map!): Map!
# sets a single UI key value
"overwrites the entire plugin configuration for the given plugin"
configurePlugin(plugin_id: ID!, input: Map!): Map!
"""
overwrites the UI configuration
if input is provided, then the entire UI configuration is replaced
if partial is provided, then the partial UI configuration is merged into the existing UI configuration
"""
configureUI(input: Map, partial: Map): Map!
"""
sets a single UI key value
key is a dot separated path to the value
"""
configureUISetting(key: String!, value: Any): Map!
"Generate and set (or clear) API key"
@@ -375,6 +475,8 @@ type Mutation {
metadataAutoTag(input: AutoTagMetadataInput!): ID!
"Clean metadata. Returns the job ID"
metadataClean(input: CleanMetadataInput!): ID!
"Clean generated files. Returns the job ID"
metadataCleanGenerated(input: CleanGeneratedInput!): ID!
"Identifies scenes using scrapers. Returns the job ID"
metadataIdentify(input: IdentifyMetadataInput!): ID!
@@ -394,14 +496,60 @@ type Mutation {
"Reload scrapers"
reloadScrapers: Boolean!
"Run plugin task. Returns the job ID"
"""
Enable/disable plugins - enabledMap is a map of plugin IDs to enabled booleans.
Plugins not in the map are not affected.
"""
setPluginsEnabled(enabledMap: BoolMap!): Boolean!
"""
Run a plugin task.
If task_name is provided, then the task must exist in the plugin config and the tasks configuration
will be used to run the plugin.
If no task_name is provided, then the plugin will be executed with the arguments provided only.
Returns the job ID
"""
runPluginTask(
plugin_id: ID!
task_name: String!
args: [PluginArgInput!]
"if provided, then the default args will be applied"
task_name: String
"displayed in the task queue"
description: String
args: [PluginArgInput!] @deprecated(reason: "Use args_map instead")
args_map: Map
): ID!
"""
Runs a plugin operation. The operation is run immediately and does not use the job queue.
Returns a map of the result.
"""
runPluginOperation(plugin_id: ID!, args: Map): Any
reloadPlugins: Boolean!
"""
Installs the given packages.
If a package is already installed, it will be updated if needed..
If an error occurs when installing a package, the job will continue to install the remaining packages.
Returns the job ID
"""
installPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!
"""
Updates the given packages.
If a package is not installed, it will not be installed.
If a package does not need to be updated, it will not be updated.
If no packages are provided, all packages of the given type will be updated.
If an error occurs when updating a package, the job will continue to update the remaining packages.
Returns the job ID.
"""
updatePackages(type: PackageType!, packages: [PackageSpecInput!]): ID!
"""
Uninstalls the given packages.
If an error occurs when uninstalling a package, the job will continue to uninstall the remaining packages.
Returns the job ID
"""
uninstallPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!
stopJob(job_id: ID!): Boolean!
stopAllJobs: Boolean!

View File

@@ -2,6 +2,8 @@ input SetupInput {
"Empty to indicate $HOME/.stash/config.yml default"
configLocation: String!
stashes: [StashConfigInput!]!
"True if SFW content mode is enabled"
sfwContentMode: Boolean
"Empty to indicate default"
databaseFile: String!
"Empty to indicate default"
@@ -67,18 +69,26 @@ input ConfigGeneralInput {
databasePath: String
"Path to backup directory"
backupDirectoryPath: String
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
deleteTrashPath: String
"Path to generated files"
generatedPath: String
"Path to import/export files"
metadataPath: String
"Path to scrapers"
scrapersPath: String
"Path to plugins"
pluginsPath: String
"Path to cache"
cachePath: String
"Path to blobs - required for filesystem blob storage"
blobsPath: String
"Where to store blobs"
blobsStorage: BlobsStorageType
"Path to the ffmpeg binary. If empty, stash will attempt to find it in the path or config directory"
ffmpegPath: String
"Path to the ffprobe binary. If empty, stash will attempt to find it in the path or config directory"
ffprobePath: String
"Whether to calculate MD5 checksums for scene video files"
calculateMD5: Boolean
"Hash algorithm to use for generated file naming"
@@ -139,8 +149,6 @@ input ConfigGeneralInput {
password: String
"Maximum session cookie age"
maxSessionAge: Int
"Comma separated list of proxies to allow traffic from"
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"Name of the log file"
logFile: String
"Whether to also output to stderr"
@@ -149,6 +157,8 @@ input ConfigGeneralInput {
logLevel: String
"Whether to log http access"
logAccess: Boolean
"Maximum log size"
logFileMaxSize: Int
"True if galleries should be created from folders with images"
createGalleriesFromFolders: Boolean
"Regex used to identify images as gallery covers"
@@ -165,25 +175,15 @@ input ConfigGeneralInput {
imageExcludes: [String!]
"Custom Performer Image Location"
customPerformerImageLocation: String
"Scraper user agent string"
scraperUserAgent: String
@deprecated(
reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead"
)
"Scraper CDP path. Path to chrome executable or remote address"
scraperCDPPath: String
@deprecated(
reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead"
)
"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!]
"Python path - resolved using path if unset"
pythonPath: String
"Source of scraper packages"
scraperPackageSources: [PackageSourceInput!]
"Source of plugin packages"
pluginPackageSources: [PackageSourceInput!]
}
type ConfigGeneralResult {
@@ -193,6 +193,8 @@ type ConfigGeneralResult {
databasePath: String!
"Path to backup directory"
backupDirectoryPath: String!
"Path to trash directory - if set, deleted files will be moved here instead of being permanently deleted"
deleteTrashPath: String!
"Path to generated files"
generatedPath: String!
"Path to import/export files"
@@ -201,12 +203,18 @@ type ConfigGeneralResult {
configFilePath: String!
"Path to scrapers"
scrapersPath: String!
"Path to plugins"
pluginsPath: String!
"Path to cache"
cachePath: String!
"Path to blobs - required for filesystem blob storage"
blobsPath: String!
"Where to store blobs"
blobsStorage: BlobsStorageType!
"Path to the ffmpeg binary. If empty, stash will attempt to find it in the path or config directory"
ffmpegPath: String!
"Path to the ffprobe binary. If empty, stash will attempt to find it in the path or config directory"
ffprobePath: String!
"Whether to calculate MD5 checksums for scene video files"
calculateMD5: Boolean!
"Hash algorithm to use for generated file naming"
@@ -269,8 +277,6 @@ type ConfigGeneralResult {
password: String!
"Maximum session cookie age"
maxSessionAge: Int!
"Comma separated list of proxies to allow traffic from"
trustedProxies: [String!] @deprecated(reason: "no longer supported")
"Name of the log file"
logFile: String
"Whether to also output to stderr"
@@ -279,6 +285,8 @@ type ConfigGeneralResult {
logLevel: String!
"Whether to log http access"
logAccess: Boolean!
"Maximum log size"
logFileMaxSize: Int!
"Array of video file extensions"
videoExtensions: [String!]!
"Array of image file extensions"
@@ -295,19 +303,15 @@ type ConfigGeneralResult {
imageExcludes: [String!]!
"Custom Performer Image Location"
customPerformerImageLocation: String
"Scraper user agent string"
scraperUserAgent: String
@deprecated(reason: "use ConfigResult.scraping instead")
"Scraper CDP path. Path to chrome executable or remote address"
scraperCDPPath: String
@deprecated(reason: "use ConfigResult.scraping instead")
"Whether the scraper should check for invalid certificates"
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!
"Source of scraper packages"
scraperPackageSources: [PackageSource!]!
"Source of plugin packages"
pluginPackageSources: [PackageSource!]!
}
input ConfigDisableDropdownCreateInput {
@@ -315,6 +319,7 @@ input ConfigDisableDropdownCreateInput {
tag: Boolean
studio: Boolean
movie: Boolean
gallery: Boolean
}
enum ImageLightboxDisplayMode {
@@ -335,6 +340,7 @@ input ConfigImageLightboxInput {
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int
disableAnimation: Boolean
}
type ConfigImageLightboxResult {
@@ -344,9 +350,13 @@ type ConfigImageLightboxResult {
resetZoomOnNav: Boolean
scrollMode: ImageLightboxScrollMode
scrollAttemptsBeforeChange: Int!
disableAnimation: Boolean
}
input ConfigInterfaceInput {
"True if SFW content mode is enabled"
sfwContentMode: Boolean
"Ordered list of items that should be shown in the menu"
menuItems: [String!]
@@ -388,9 +398,6 @@ input ConfigInterfaceInput {
"Interface language"
language: String
"Slideshow Delay"
slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay")
imageLightbox: ConfigImageLightboxInput
"Set to true to disable creating new objects via the dropdown menus"
@@ -413,9 +420,13 @@ type ConfigDisableDropdownCreate {
tag: Boolean!
studio: Boolean!
movie: Boolean!
gallery: Boolean!
}
type ConfigInterfaceResult {
"True if SFW content mode is enabled"
sfwContentMode: Boolean!
"Ordered list of items that should be shown in the menu"
menuItems: [String!]
@@ -461,15 +472,10 @@ type ConfigInterfaceResult {
"Interface language"
language: String
"Slideshow Delay"
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
@@ -483,6 +489,8 @@ input ConfigDLNAInput {
serverName: String
"True if DLNA service should be enabled by default"
enabled: Boolean
"Defaults to 1338"
port: Int
"List of IPs whitelisted for DLNA service"
whitelistedIPs: [String!]
"List of interfaces to run DLNA on. Empty for all"
@@ -495,6 +503,8 @@ type ConfigDLNAResult {
serverName: String!
"True if DLNA service should be enabled by default"
enabled: Boolean!
"Defaults to 1338"
port: Int!
"List of IPs whitelisted for DLNA service"
whitelistedIPs: [String!]!
"List of interfaces to run DLNA on. Empty for all"
@@ -557,6 +567,7 @@ type ConfigResult {
scraping: ConfigScrapingResult!
defaults: ConfigDefaultSettingsResult!
ui: Map!
plugins(include: [ID!]): PluginConfigMap!
}
"Directory structure of a path"

View File

@@ -7,8 +7,11 @@ type Folder {
id: ID!
path: String!
parent_folder_id: ID
zip_file_id: ID
parent_folder_id: ID @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder
zip_file: BasicFile
mod_time: Time!
@@ -21,12 +24,37 @@ interface BaseFile {
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
updated_at: Time!
}
type BasicFile implements BaseFile {
id: ID!
path: String!
basename: String!
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
@@ -38,12 +66,16 @@ type VideoFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
format: String!
@@ -64,14 +96,19 @@ type ImageFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
format: String!
width: Int!
height: Int!
@@ -86,12 +123,16 @@ type GalleryFile implements BaseFile {
path: String!
basename: String!
parent_folder_id: ID!
zip_file_id: ID
parent_folder_id: ID! @deprecated(reason: "Use parent_folder instead")
zip_file_id: ID @deprecated(reason: "Use zip_file instead")
parent_folder: Folder!
zip_file: BasicFile
mod_time: Time!
size: Int64!
fingerprint(type: String!): String
fingerprints: [Fingerprint!]!
created_at: Time!
@@ -109,3 +150,34 @@ input MoveFilesInput {
"valid only for single file id. If empty, existing basename is used"
destination_basename: String
}
input SetFingerprintsInput {
type: String!
"an null value will remove the fingerprint"
value: String
}
input FileSetFingerprintsInput {
id: ID!
"only supplied fingerprint types will be modified"
fingerprints: [SetFingerprintsInput!]!
}
type FindFilesResultType {
count: Int!
"Total megapixels of any image files"
megapixels: Float!
"Total duration in seconds of any video files"
duration: Float!
"Total file size in bytes"
size: Int!
files: [BaseFile!]!
}
type FindFoldersResultType {
count: Int!
folders: [Folder!]!
}

View File

@@ -8,6 +8,7 @@ input FindFilterType {
page: Int
"use per_page = -1 to indicate all results. Defaults to 25."
per_page: Int
# TODO - this should be refactored to not use a string
sort: String
direction: SortDirectionEnum
}
@@ -61,6 +62,19 @@ input ResolutionCriterionInput {
modifier: CriterionModifier!
}
enum OrientationEnum {
"Landscape"
LANDSCAPE
"Portrait"
PORTRAIT
"Square"
SQUARE
}
input OrientationCriterionInput {
value: [OrientationEnum!]!
}
input PHashDuplicationCriterionInput {
duplicated: Boolean
"Currently unimplemented"
@@ -77,6 +91,12 @@ input StashIDCriterionInput {
modifier: CriterionModifier!
}
input CustomFieldCriterionInput {
field: String!
value: [Any!]
modifier: CriterionModifier!
}
input PerformerFilterType {
AND: PerformerFilterType
OR: PerformerFilterType
@@ -98,8 +118,6 @@ input PerformerFilterType {
country: StringCriterionInput
"Filter by eye color"
eye_color: StringCriterionInput
"Filter by height"
height: StringCriterionInput @deprecated(reason: "Use height_cm instead")
"Filter by height in cm"
height_cm: IntCriterionInput
"Filter by measurements"
@@ -132,16 +150,12 @@ input PerformerFilterType {
image_count: IntCriterionInput
"Filter by gallery count"
gallery_count: IntCriterionInput
"Filter by play count"
play_count: IntCriterionInput
"Filter by o count"
o_counter: IntCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by url"
@@ -154,6 +168,8 @@ input PerformerFilterType {
death_year: IntCriterionInput
"Filter by studios where performer appears in scene/image/gallery"
studios: HierarchicalMultiCriterionInput
"Filter by groups where performer appears in scene"
groups: HierarchicalMultiCriterionInput
"Filter by performers where performer appears with another performer in scene/image/gallery"
performers: MultiCriterionInput
"Filter by autotag ignore value"
@@ -162,21 +178,33 @@ input PerformerFilterType {
birthdate: DateCriterionInput
"Filter by death date"
death_date: DateCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
custom_fields: [CustomFieldCriterionInput!]
}
input SceneMarkerFilterType {
"Filter to only include scene markers with this tag"
tag_id: ID @deprecated(reason: "use tags filter instead")
"Filter to only include scene markers with these tags"
tags: HierarchicalMultiCriterionInput
"Filter to only include scene markers attached to a scene with these tags"
scene_tags: HierarchicalMultiCriterionInput
"Filter to only include scene markers with these performers"
performers: MultiCriterionInput
"Filter to only include scene markers from these scenes"
scenes: MultiCriterionInput
"Filter by duration (in seconds)"
duration: FloatCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
@@ -187,6 +215,8 @@ input SceneMarkerFilterType {
scene_created_at: TimestampCriterionInput
"Filter by lscene ast update time"
scene_updated_at: TimestampCriterionInput
"Filter by related scenes that meet this criteria"
scene_filter: SceneFilterType
}
input SceneFilterType {
@@ -212,9 +242,6 @@ input SceneFilterType {
path: StringCriterionInput
"Filter by file count"
file_count: IntCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by organized"
@@ -225,6 +252,12 @@ input SceneFilterType {
duplicated: PHashDuplicationCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter by frame rate"
framerate: IntCriterionInput
"Filter by bit rate"
bitrate: IntCriterionInput
"Filter by video codec"
video_codec: StringCriterionInput
"Filter by audio codec"
@@ -238,7 +271,11 @@ input SceneFilterType {
"Filter to only include scenes with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include scenes with this movie"
movies: MultiCriterionInput
movies: MultiCriterionInput @deprecated(reason: "use groups instead")
"Filter to only include scenes with this group"
groups: HierarchicalMultiCriterionInput
"Filter to only include scenes with this gallery"
galleries: MultiCriterionInput
"Filter to only include scenes with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
@@ -254,9 +291,6 @@ input SceneFilterType {
"Filter by performer count"
performer_count: IntCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
"Filter by url"
url: StringCriterionInput
@@ -272,24 +306,45 @@ input SceneFilterType {
play_count: IntCriterionInput
"Filter by play duration (in seconds)"
play_duration: IntCriterionInput
"Filter by scene last played time"
last_played_at: TimestampCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related movies that meet this criteria"
movies_filter: MovieFilterType
@deprecated(reason: "use groups_filter instead")
"Filter by related groups that meet this criteria"
groups_filter: GroupFilterType
"Filter by related markers that meet this criteria"
markers_filter: SceneMarkerFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
}
input MovieFilterType {
AND: MovieFilterType
OR: MovieFilterType
NOT: MovieFilterType
name: StringCriterionInput
director: StringCriterionInput
synopsis: StringCriterionInput
"Filter by duration (in seconds)"
duration: IntCriterionInput
"Filter by rating"
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"
@@ -300,12 +355,70 @@ input MovieFilterType {
url: StringCriterionInput
"Filter to only include movies where performer appears in a scene"
performers: MultiCriterionInput
"Filter to only include movies with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
}
input GroupFilterType {
AND: GroupFilterType
OR: GroupFilterType
NOT: GroupFilterType
name: StringCriterionInput
director: StringCriterionInput
synopsis: StringCriterionInput
"Filter by duration (in seconds)"
duration: IntCriterionInput
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter to only include groups with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include groups missing this property"
is_missing: String
"Filter by url"
url: StringCriterionInput
"Filter to only include groups where performer appears in a scene"
performers: MultiCriterionInput
"Filter to only include groups with these tags"
tags: HierarchicalMultiCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by date"
date: DateCriterionInput
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by o-counter"
o_counter: IntCriterionInput
"Filter by containing groups"
containing_groups: HierarchicalMultiCriterionInput
"Filter by sub groups"
sub_groups: HierarchicalMultiCriterionInput
"Filter by number of containing groups the group has"
containing_group_count: IntCriterionInput
"Filter by number of sub-groups the group has"
sub_group_count: IntCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
}
input StudioFilterType {
@@ -318,29 +431,37 @@ input StudioFilterType {
"Filter to only include studios with this parent studio"
parents: MultiCriterionInput
"Filter by StashID"
stash_id: StringCriterionInput
@deprecated(reason: "Use stash_id_endpoint instead")
"Filter by StashID"
stash_id_endpoint: StashIDCriterionInput
"Filter to only include studios with these tags"
tags: HierarchicalMultiCriterionInput
"Filter to only include studios missing this property"
is_missing: String
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by favorite"
favorite: Boolean
"Filter by scene count"
scene_count: IntCriterionInput
"Filter by image count"
image_count: IntCriterionInput
"Filter by gallery count"
gallery_count: IntCriterionInput
"Filter by tag count"
tag_count: IntCriterionInput
"Filter by url"
url: StringCriterionInput
"Filter by studio aliases"
aliases: StringCriterionInput
"Filter by subsidiary studio count"
child_count: IntCriterionInput
"Filter by autotag ignore value"
ignore_auto_tag: Boolean
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
@@ -366,9 +487,6 @@ input GalleryFilterType {
is_missing: String
"Filter to include/exclude galleries that were created from zip"
is_zip: Boolean
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by organized"
@@ -377,6 +495,8 @@ input GalleryFilterType {
average_resolution: ResolutionCriterionInput
"Filter to only include galleries that have chapters. `true` or `false`"
has_chapters: String
"Filter to only include galleries with these scenes"
scenes: MultiCriterionInput
"Filter to only include galleries with this studio"
studios: HierarchicalMultiCriterionInput
"Filter to only include galleries with these tags"
@@ -403,6 +523,25 @@ input GalleryFilterType {
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
"Filter by studio code"
code: StringCriterionInput
"Filter by photographer"
photographer: StringCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
"Filter by related folders that meet this criteria"
folders_filter: FolderFilterType
}
input TagFilterType {
@@ -413,9 +552,15 @@ input TagFilterType {
"Filter by tag name"
name: StringCriterionInput
"Filter by tag sort_name"
sort_name: StringCriterionInput
"Filter by tag aliases"
aliases: StringCriterionInput
"Filter by favorite"
favorite: Boolean
"Filter by tag description"
description: StringCriterionInput
@@ -434,6 +579,15 @@ input TagFilterType {
"Filter by number of performers with this tag"
performer_count: IntCriterionInput
"Filter by number of studios with this tag"
studio_count: IntCriterionInput
"Filter by number of movies with this tag"
movie_count: IntCriterionInput
"Filter by number of group with this tag"
group_count: IntCriterionInput
"Filter by number of markers with this tag"
marker_count: IntCriterionInput
@@ -452,6 +606,13 @@ input TagFilterType {
"Filter by autotag ignore value"
ignore_auto_tag: Boolean
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
@@ -465,6 +626,7 @@ input ImageFilterType {
NOT: ImageFilterType
title: StringCriterionInput
details: StringCriterionInput
" Filter by image id"
id: IntCriterionInput
@@ -474,9 +636,6 @@ input ImageFilterType {
path: StringCriterionInput
"Filter by file count"
file_count: IntCriterionInput
"Filter by rating"
rating: IntCriterionInput
@deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: IntCriterionInput
"Filter by date"
@@ -489,6 +648,8 @@ input ImageFilterType {
o_counter: IntCriterionInput
"Filter by resolution"
resolution: ResolutionCriterionInput
"Filter by orientation"
orientation: OrientationCriterionInput
"Filter to only include images missing this property"
is_missing: String
"Filter to only include images with this studio"
@@ -505,12 +666,127 @@ input ImageFilterType {
performer_count: IntCriterionInput
"Filter images that have performers that have been favorited"
performer_favorite: Boolean
"Filter images by performer age at time of image"
performer_age: IntCriterionInput
"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
"Filter by studio code"
code: StringCriterionInput
"Filter by photographer"
photographer: StringCriterionInput
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by related performers that meet this criteria"
performers_filter: PerformerFilterType
"Filter by related studios that meet this criteria"
studios_filter: StudioFilterType
"Filter by related tags that meet this criteria"
tags_filter: TagFilterType
"Filter by related files that meet this criteria"
files_filter: FileFilterType
}
input FileFilterType {
AND: FileFilterType
OR: FileFilterType
NOT: FileFilterType
path: StringCriterionInput
basename: StringCriterionInput
dir: StringCriterionInput
parent_folder: HierarchicalMultiCriterionInput
zip_file: MultiCriterionInput
"Filter by modification time"
mod_time: TimestampCriterionInput
"Filter files that have an exact match available"
duplicated: PHashDuplicationCriterionInput
"find files based on hash"
hashes: [FingerprintFilterInput!]
video_file_filter: VideoFileFilterInput
image_file_filter: ImageFileFilterInput
scene_count: IntCriterionInput
image_count: IntCriterionInput
gallery_count: IntCriterionInput
"Filter by related scenes that meet this criteria"
scenes_filter: SceneFilterType
"Filter by related images that meet this criteria"
images_filter: ImageFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
}
input FolderFilterType {
AND: FolderFilterType
OR: FolderFilterType
NOT: FolderFilterType
path: StringCriterionInput
parent_folder: HierarchicalMultiCriterionInput
zip_file: MultiCriterionInput
"Filter by modification time"
mod_time: TimestampCriterionInput
gallery_count: IntCriterionInput
"Filter by files that meet this criteria"
files_filter: FileFilterType
"Filter by related galleries that meet this criteria"
galleries_filter: GalleryFilterType
"Filter by creation time"
created_at: TimestampCriterionInput
"Filter by last update time"
updated_at: TimestampCriterionInput
}
input VideoFileFilterInput {
resolution: ResolutionCriterionInput
orientation: OrientationCriterionInput
framerate: IntCriterionInput
bitrate: IntCriterionInput
format: StringCriterionInput
video_codec: StringCriterionInput
audio_codec: StringCriterionInput
"in seconds"
duration: IntCriterionInput
captions: StringCriterionInput
interactive: Boolean
interactive_speed: IntCriterionInput
}
input ImageFileFilterInput {
format: StringCriterionInput
resolution: ResolutionCriterionInput
orientation: OrientationCriterionInput
}
input FingerprintFilterInput {
type: String!
value: String!
"Hamming distance - defaults to 0"
distance: Int
}
enum CriterionModifier {
@@ -565,6 +841,7 @@ input MultiCriterionInput {
input GenderCriterionInput {
value: GenderEnum
value_list: [GenderEnum!]
modifier: CriterionModifier!
}
@@ -605,6 +882,7 @@ enum FilterMode {
GALLERIES
SCENE_MARKERS
MOVIES
GROUPS
TAGS
IMAGES
}

View File

@@ -1,21 +1,23 @@
type GalleryPathsType {
cover: String!
preview: String! # Resolver
}
"Gallery type"
type Gallery {
id: ID!
checksum: String! @deprecated(reason: "Use files.fingerprints")
path: String @deprecated(reason: "Use files.path")
title: String
code: String
url: String @deprecated(reason: "Use urls")
urls: [String!]!
date: String
details: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
photographer: String
# rating expressed as 1-100
rating100: Int
organized: Boolean!
created_at: Time!
updated_at: Time!
file_mod_time: Time @deprecated(reason: "Use files.mod_time")
files: [GalleryFile!]!
folder: Folder
@@ -27,19 +29,20 @@ type Gallery {
tags: [Tag!]!
performers: [Performer!]!
"The images in the gallery"
images: [Image!]! @deprecated(reason: "Use findImages")
cover: Image
paths: GalleryPathsType! # Resolver
image(index: Int!): Image!
}
input GalleryCreateInput {
title: String!
code: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
date: String
details: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
photographer: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
@@ -53,12 +56,12 @@ input GalleryUpdateInput {
clientMutationId: String
id: ID!
title: String
code: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
date: String
details: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
photographer: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
@@ -73,12 +76,12 @@ input GalleryUpdateInput {
input BulkGalleryUpdateInput {
clientMutationId: String
ids: [ID!]
code: String
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
date: String
details: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
photographer: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
@@ -113,3 +116,12 @@ input GalleryRemoveInput {
gallery_id: ID!
image_ids: [ID!]!
}
input GallerySetCoverInput {
gallery_id: ID!
cover_image_id: ID!
}
input GalleryResetCoverInput {
gallery_id: ID!
}

View File

@@ -0,0 +1,139 @@
"GroupDescription represents a relationship to a group with a description of the relationship"
type GroupDescription {
group: Group!
description: String
}
type Group {
id: ID!
name: String!
aliases: String
"Duration in seconds"
duration: Int
date: String
# rating expressed as 1-100
rating100: Int
studio: Studio
director: String
synopsis: String
urls: [String!]!
tags: [Tag!]!
created_at: Time!
updated_at: Time!
containing_groups: [GroupDescription!]!
sub_groups: [GroupDescription!]!
front_image_path: String # Resolver
back_image_path: String # Resolver
scene_count(depth: Int): Int! # Resolver
performer_count(depth: Int): Int! # Resolver
sub_group_count(depth: Int): Int! # Resolver
scenes: [Scene!]!
o_counter: Int # Resolver
}
input GroupDescriptionInput {
group_id: ID!
description: String
}
input GroupCreateInput {
name: String!
aliases: String
"Duration in seconds"
duration: Int
date: String
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
synopsis: String
urls: [String!]
tag_ids: [ID!]
containing_groups: [GroupDescriptionInput!]
sub_groups: [GroupDescriptionInput!]
"This should be a URL or a base64 encoded data URL"
front_image: String
"This should be a URL or a base64 encoded data URL"
back_image: String
}
input GroupUpdateInput {
id: ID!
name: String
aliases: String
duration: Int
date: String
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
synopsis: String
urls: [String!]
tag_ids: [ID!]
containing_groups: [GroupDescriptionInput!]
sub_groups: [GroupDescriptionInput!]
"This should be a URL or a base64 encoded data URL"
front_image: String
"This should be a URL or a base64 encoded data URL"
back_image: String
}
input BulkUpdateGroupDescriptionsInput {
groups: [GroupDescriptionInput!]!
mode: BulkUpdateIdMode!
}
input BulkGroupUpdateInput {
clientMutationId: String
ids: [ID!]
# rating expressed as 1-100
rating100: Int
studio_id: ID
director: String
urls: BulkUpdateStrings
tag_ids: BulkUpdateIds
containing_groups: BulkUpdateGroupDescriptionsInput
sub_groups: BulkUpdateGroupDescriptionsInput
}
input GroupDestroyInput {
id: ID!
}
input ReorderSubGroupsInput {
"ID of the group to reorder sub groups for"
group_id: ID!
"""
IDs of the sub groups to reorder. These must be a subset of the current sub groups.
Sub groups will be inserted in this order at the insert_index
"""
sub_group_ids: [ID!]!
"The sub-group ID at which to insert the sub groups"
insert_at_id: ID!
"If true, the sub groups will be inserted after the insert_index, otherwise they will be inserted before"
insert_after: Boolean
}
type FindGroupsResultType {
count: Int!
groups: [Group!]!
}
input GroupSubGroupAddInput {
containing_group_id: ID!
sub_groups: [GroupDescriptionInput!]!
"The index at which to insert the sub groups. If not provided, the sub groups will be appended to the end"
insert_index: Int
}
input GroupSubGroupRemoveInput {
containing_group_id: ID!
sub_group_ids: [ID!]!
}

View File

@@ -1,23 +1,19 @@
type Image {
id: ID!
checksum: String @deprecated(reason: "Use files.fingerprints")
title: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
code: String
# rating expressed as 1-100
rating100: Int
url: String @deprecated(reason: "Use urls")
urls: [String!]!
date: String
details: String
photographer: String
o_counter: Int
organized: Boolean!
path: String! @deprecated(reason: "Use files.path")
created_at: Time!
updated_at: Time!
file_mod_time: Time @deprecated(reason: "Use files.mod_time")
file: ImageFileType! @deprecated(reason: "Use visual_files")
files: [ImageFile!]! @deprecated(reason: "Use visual_files")
visual_files: [VisualFile!]!
paths: ImagePathsType! # Resolver
@@ -44,14 +40,15 @@ input ImageUpdateInput {
clientMutationId: String
id: ID!
title: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
code: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
url: String @deprecated(reason: "Use urls")
urls: [String!]
date: String
details: String
photographer: String
studio_id: ID
performer_ids: [ID!]
@@ -65,14 +62,15 @@ input BulkImageUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
code: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
date: String
details: String
photographer: String
studio_id: ID
performer_ids: BulkUpdateIds

View File

@@ -4,6 +4,7 @@ enum JobStatus {
FINISHED
STOPPING
CANCELLED
FAILED
}
type Job {
@@ -15,6 +16,7 @@ type Job {
startTime: Time
endTime: Time
addTime: Time!
error: String
}
input FindJobInput {

View File

@@ -1,6 +1,3 @@
"Log entries"
scalar Time
enum LogLevel {
Trace
Debug

View File

@@ -1,5 +1,3 @@
scalar Upload
input GenerateMetadataInput {
covers: Boolean
sprites: Boolean
@@ -14,6 +12,7 @@ input GenerateMetadataInput {
forceTranscodes: Boolean
phashes: Boolean
interactiveHeatmapsSpeeds: Boolean
imageThumbnails: Boolean
clipPreviews: Boolean
"scene ids to generate for"
@@ -50,6 +49,7 @@ type GenerateMetadataOptions {
transcodes: Boolean
phashes: Boolean
interactiveHeatmapsSpeeds: Boolean
imageThumbnails: Boolean
clipPreviews: Boolean
}
@@ -75,19 +75,8 @@ input ScanMetaDataFilterInput {
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 @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 @deprecated(reason: "Not implemented")
"Forces a rescan on files even if modification time is unchanged"
rescan: Boolean
"Generate covers during scan"
scanGenerateCovers: Boolean
"Generate previews during scan"
@@ -108,10 +97,8 @@ input ScanMetadataInput {
}
type ScanMetadataOptions {
"Set name, date, details from metadata (if present)"
useFileMetadata: Boolean! @deprecated(reason: "Not implemented")
"Strip file extension from title"
stripFileExtension: Boolean! @deprecated(reason: "Not implemented")
"Forces a rescan on files even if modification time is unchanged"
rescan: Boolean!
"Generate covers during scan"
scanGenerateCovers: Boolean!
"Generate previews during scan"
@@ -135,6 +122,26 @@ input CleanMetadataInput {
dryRun: Boolean!
}
input CleanGeneratedInput {
"Clean blob files without blob entries"
blobFiles: Boolean
"Clean sprite and vtt files without scene entries"
sprites: Boolean
"Clean preview files without scene entries"
screenshots: Boolean
"Clean scene transcodes without scene entries"
transcodes: Boolean
"Clean marker files without marker entries"
markers: Boolean
"Clean image thumbnails/clips without image entries"
imageThumbnails: Boolean
"Do a dry run. Don't delete any files"
dryRun: Boolean
}
input AutoTagMetadataInput {
"Paths to tag, null for all files"
paths: [String!]
@@ -277,7 +284,8 @@ input ExportObjectsInput {
studios: ExportObjectTypeInput
performers: ExportObjectTypeInput
tags: ExportObjectTypeInput
movies: ExportObjectTypeInput
groups: ExportObjectTypeInput
movies: ExportObjectTypeInput @deprecated(reason: "Use groups instead")
galleries: ExportObjectTypeInput
includeDependencies: Boolean
}
@@ -320,8 +328,22 @@ type SystemStatus {
configPath: String
appSchema: Int!
status: SystemStatusEnum!
os: String!
workingDir: String!
homeDir: String!
ffmpegPath: String
ffprobePath: String
}
input MigrateInput {
backupPath: String!
}
input CustomFieldsInput {
"If populated, the entire custom fields map will be replaced with this value"
full: Map
"If populated, only the keys in this map will be updated"
partial: Map
"Remove any keys in this list"
remove: [String!]
}

View File

@@ -1,25 +1,24 @@
type Movie {
id: ID!
name: String!
checksum: String! @deprecated(reason: "MD5 hash of name, use name directly")
aliases: String
"Duration in seconds"
duration: Int
date: String
# 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
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]!
tags: [Tag!]!
created_at: Time!
updated_at: Time!
front_image_path: String # Resolver
back_image_path: String # Resolver
scene_count: Int! # Resolver
scene_count(depth: Int): Int! # Resolver
scenes: [Scene!]!
}
@@ -29,14 +28,14 @@ input MovieCreateInput {
"Duration in seconds"
duration: Int
date: String
# 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
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
front_image: String
"This should be a URL or a base64 encoded data URL"
@@ -49,14 +48,14 @@ input MovieUpdateInput {
aliases: String
duration: Int
date: String
# 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
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
front_image: String
"This should be a URL or a base64 encoded data URL"
@@ -66,12 +65,12 @@ input MovieUpdateInput {
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
urls: BulkUpdateStrings
tag_ids: BulkUpdateIds
}
input MovieDestroyInput {

View File

@@ -0,0 +1,36 @@
enum PackageType {
Scraper
Plugin
}
type Package {
package_id: String!
name: String!
version: String
date: Timestamp
requires: [Package!]!
sourceURL: String!
"The version of this package currently available from the remote source"
source_package: Package
metadata: Map!
}
input PackageSpecInput {
id: String!
sourceURL: String!
}
type PackageSource {
name: String
url: String!
local_path: String
}
input PackageSourceInput {
name: String
url: String!
local_path: String
}

View File

@@ -14,18 +14,17 @@ enum CircumisedEnum {
type Performer {
id: ID!
checksum: String @deprecated(reason: "Not used")
name: String!
disambiguation: String
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
gender: GenderEnum
twitter: String
instagram: String
twitter: String @deprecated(reason: "Use urls")
instagram: String @deprecated(reason: "Use urls")
birthdate: String
ethnicity: String
country: String
eye_color: String
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
@@ -34,7 +33,6 @@ type Performer {
career_length: String
tattoos: String
piercings: String
aliases: String @deprecated(reason: "Use alias_list")
alias_list: [String!]!
favorite: Boolean!
tags: [Tag!]!
@@ -44,13 +42,12 @@ type Performer {
scene_count: Int! # Resolver
image_count: Int! # Resolver
gallery_count: Int! # Resolver
movie_count: Int! # Resolver
group_count: Int! # Resolver
movie_count: Int! @deprecated(reason: "use group_count instead") # Resolver
performer_count: Int! # Resolver
o_counter: Int # Resolver
scenes: [Scene!]!
stash_ids: [StashID!]!
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
@@ -59,20 +56,22 @@ type Performer {
weight: Int
created_at: Time!
updated_at: Time!
movies: [Movie!]!
groups: [Group!]!
movies: [Movie!]! @deprecated(reason: "use groups instead")
custom_fields: Map!
}
input PerformerCreateInput {
name: String!
disambiguation: String
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
gender: GenderEnum
birthdate: String
ethnicity: String
country: String
eye_color: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
@@ -81,17 +80,14 @@ input PerformerCreateInput {
career_length: String
tattoos: String
piercings: String
aliases: String @deprecated(reason: "Use alias_list")
alias_list: [String!]
twitter: String
instagram: String
twitter: String @deprecated(reason: "Use urls")
instagram: String @deprecated(reason: "Use urls")
favorite: Boolean
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
@@ -99,20 +95,21 @@ input PerformerCreateInput {
hair_color: String
weight: Int
ignore_auto_tag: Boolean
custom_fields: Map
}
input PerformerUpdateInput {
id: ID!
name: String
disambiguation: String
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
gender: GenderEnum
birthdate: String
ethnicity: String
country: String
eye_color: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
@@ -121,17 +118,14 @@ input PerformerUpdateInput {
career_length: String
tattoos: String
piercings: String
aliases: String @deprecated(reason: "Use alias_list")
alias_list: [String!]
twitter: String
instagram: String
twitter: String @deprecated(reason: "Use urls")
instagram: String @deprecated(reason: "Use urls")
favorite: Boolean
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
@@ -139,6 +133,8 @@ input PerformerUpdateInput {
hair_color: String
weight: Int
ignore_auto_tag: Boolean
custom_fields: CustomFieldsInput
}
input BulkUpdateStrings {
@@ -150,14 +146,13 @@ input BulkPerformerUpdateInput {
clientMutationId: String
ids: [ID!]
disambiguation: String
url: String
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
gender: GenderEnum
birthdate: String
ethnicity: String
country: String
eye_color: String
# height must be parsable into an integer
height: String @deprecated(reason: "Use height_cm")
height_cm: Int
measurements: String
fake_tits: String
@@ -166,14 +161,11 @@ input BulkPerformerUpdateInput {
career_length: String
tattoos: String
piercings: String
aliases: String @deprecated(reason: "Use alias_list")
alias_list: BulkUpdateStrings
twitter: String
instagram: String
twitter: String @deprecated(reason: "Use urls")
instagram: String @deprecated(reason: "Use urls")
favorite: Boolean
tag_ids: BulkUpdateIds
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
details: String
@@ -181,6 +173,8 @@ input BulkPerformerUpdateInput {
hair_color: String
weight: Int
ignore_auto_tag: Boolean
custom_fields: CustomFieldsInput
}
input PerformerDestroyInput {

View File

@@ -1,3 +1,10 @@
type PluginPaths {
# path to javascript files
javascript: [String!]
# path to css files
css: [String!]
}
type Plugin {
id: ID!
name: String!
@@ -5,8 +12,19 @@ type Plugin {
url: String
version: String
enabled: Boolean!
tasks: [PluginTask!]
hooks: [PluginHook!]
settings: [PluginSetting!]
"""
Plugin IDs of plugins that this plugin depends on.
Applies only for UI plugins to indicate css/javascript load order.
"""
requires: [ID!]
paths: PluginPaths!
}
type PluginTask {
@@ -40,3 +58,16 @@ input PluginValueInput {
o: [PluginArgInput!]
a: [PluginValueInput!]
}
enum PluginSettingTypeEnum {
STRING
NUMBER
BOOLEAN
}
type PluginSetting {
name: String!
display_name: String
description: String
type: PluginSettingTypeEnum!
}

View File

@@ -1,3 +1,6 @@
"An RFC3339 timestamp"
scalar Time
"""
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"
@@ -5,9 +8,18 @@ for "5 minutes in the future"
"""
scalar Timestamp
# generic JSON object
"A String -> Any map"
scalar Map
"A String -> Boolean map"
scalar BoolMap
"A plugin ID -> Map (String -> Any map) map"
scalar PluginConfigMap
scalar Any
scalar Int64
"A multipart file upload"
scalar Upload

View File

@@ -2,7 +2,10 @@ type SceneMarker {
id: ID!
scene: Scene!
title: String!
"The required start time of the marker (in seconds). Supports decimals."
seconds: Float!
"The optional end time of the marker (in seconds). Supports decimals."
end_seconds: Float
primary_tag: Tag!
tags: [Tag!]!
created_at: Time!
@@ -18,7 +21,10 @@ type SceneMarker {
input SceneMarkerCreateInput {
title: String!
"The required start time of the marker (in seconds). Supports decimals."
seconds: Float!
"The optional end time of the marker (in seconds). Supports decimals."
end_seconds: Float
scene_id: ID!
primary_tag_id: ID!
tag_ids: [ID!]
@@ -27,12 +33,22 @@ input SceneMarkerCreateInput {
input SceneMarkerUpdateInput {
id: ID!
title: String
"The start time of the marker (in seconds). Supports decimals."
seconds: Float
"The end time of the marker (in seconds). Supports decimals."
end_seconds: Float
scene_id: ID
primary_tag_id: ID
tag_ids: [ID!]
}
input BulkSceneMarkerUpdateInput {
ids: [ID!]
title: String
primary_tag_id: ID
tag_ids: BulkUpdateIds
}
type FindSceneMarkersResultType {
count: Int!
scene_markers: [SceneMarker!]!

View File

@@ -15,7 +15,6 @@ type ScenePathsType {
stream: String # Resolver
webp: String # Resolver
vtt: String # Resolver
chapters_vtt: String @deprecated
sprite: String # Resolver
funscript: String # Resolver
interactive_heatmap: String # Resolver
@@ -27,6 +26,11 @@ type SceneMovie {
scene_index: Int
}
type SceneGroup {
group: Group!
scene_index: Int
}
type VideoCaption {
language_code: String!
caption_type: String!
@@ -34,8 +38,6 @@ type VideoCaption {
type Scene {
id: ID!
checksum: String @deprecated(reason: "Use files.fingerprints")
oshash: String @deprecated(reason: "Use files.fingerprints")
title: String
code: String
details: String
@@ -43,20 +45,15 @@ type Scene {
url: String @deprecated(reason: "Use urls")
urls: [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
organized: Boolean!
o_counter: Int
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"
@@ -66,13 +63,18 @@ type Scene {
"The number ot times a scene has been played"
play_count: Int
file: SceneFileType! @deprecated(reason: "Use files")
"Times a scene was played"
play_history: [Time!]!
"Times the o counter was incremented"
o_history: [Time!]!
files: [VideoFile!]!
paths: ScenePathsType! # Resolver
scene_markers: [SceneMarker!]!
galleries: [Gallery!]!
studio: Studio
movies: [SceneMovie!]!
groups: [SceneGroup!]!
movies: [SceneMovie!]! @deprecated(reason: "Use groups")
tags: [Tag!]!
performers: [Performer!]!
stash_ids: [StashID!]!
@@ -86,6 +88,11 @@ input SceneMovieInput {
scene_index: Int
}
input SceneGroupInput {
group_id: ID!
scene_index: Int
}
input SceneCreateInput {
title: String
code: String
@@ -94,15 +101,14 @@ input SceneCreateInput {
url: String @deprecated(reason: "Use urls")
urls: [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
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [SceneMovieInput!]
groups: [SceneGroupInput!]
movies: [SceneMovieInput!] @deprecated(reason: "Use groups")
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
cover_image: String
@@ -126,16 +132,16 @@ input SceneUpdateInput {
url: String @deprecated(reason: "Use urls")
urls: [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
@deprecated(reason: "Unsupported - Use sceneIncrementO/sceneDecrementO")
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [SceneMovieInput!]
groups: [SceneGroupInput!]
movies: [SceneMovieInput!] @deprecated(reason: "Use groups")
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
cover_image: String
@@ -147,6 +153,9 @@ input SceneUpdateInput {
play_duration: Float
"The number ot times a scene has been played"
play_count: Int
@deprecated(
reason: "Unsupported - Use sceneIncrementPlayCount/sceneDecrementPlayCount"
)
primary_file_id: ID
}
@@ -172,8 +181,6 @@ input BulkSceneUpdateInput {
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
date: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
organized: Boolean
@@ -181,7 +188,8 @@ input BulkSceneUpdateInput {
gallery_ids: BulkUpdateIds
performer_ids: BulkUpdateIds
tag_ids: BulkUpdateIds
movie_ids: BulkUpdateIds
group_ids: BulkUpdateIds
movie_ids: BulkUpdateIds @deprecated(reason: "Use group_ids")
}
input SceneDestroyInput {
@@ -266,4 +274,13 @@ input SceneMergeInput {
destination: ID!
# values defined here will override values in the destination
values: SceneUpdateInput
# if true, the source history will be combined with the destination
play_history: Boolean
o_history: Boolean
}
type HistoryMutationResult {
count: Int!
history: [Time!]!
}

View File

@@ -0,0 +1,65 @@
"A movie from a scraping operation..."
type ScrapedMovie {
stored_id: ID
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String @deprecated(reason: "use urls")
urls: [String!]
synopsis: String
studio: ScrapedStudio
tags: [ScrapedTag!]
"This should be a base64 encoded data URL"
front_image: String
"This should be a base64 encoded data URL"
back_image: String
}
input ScrapedMovieInput {
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String @deprecated(reason: "use urls")
urls: [String!]
synopsis: String
# not including tags for the input
}
"A group from a scraping operation..."
type ScrapedGroup {
stored_id: ID
name: String
aliases: String
duration: String
date: String
rating: String
director: String
urls: [String!]
synopsis: String
studio: ScrapedStudio
tags: [ScrapedTag!]
"This should be a base64 encoded data URL"
front_image: String
"This should be a base64 encoded data URL"
back_image: String
}
input ScrapedGroupInput {
name: String
aliases: String
duration: String
date: String
rating: String
director: String
urls: [String!]
synopsis: String
# not including tags for the input
}

View File

@@ -1,29 +0,0 @@
"A movie from a scraping operation..."
type ScrapedMovie {
stored_id: ID
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String
synopsis: String
studio: ScrapedStudio
"This should be a base64 encoded data URL"
front_image: String
"This should be a base64 encoded data URL"
back_image: String
}
input ScrapedMovieInput {
name: String
aliases: String
duration: String
date: String
rating: String
director: String
url: String
synopsis: String
}

View File

@@ -5,9 +5,10 @@ type ScrapedPerformer {
name: String
disambiguation: String
gender: String
url: String
twitter: String
instagram: String
url: String @deprecated(reason: "use urls")
urls: [String!]
twitter: String @deprecated(reason: "use urls")
instagram: String @deprecated(reason: "use urls")
birthdate: String
ethnicity: String
country: String
@@ -40,9 +41,10 @@ input ScrapedPerformerInput {
name: String
disambiguation: String
gender: String
url: String
twitter: String
instagram: String
url: String @deprecated(reason: "use urls")
urls: [String!]
twitter: String @deprecated(reason: "use urls")
instagram: String @deprecated(reason: "use urls")
birthdate: String
ethnicity: String
country: String

View File

@@ -10,7 +10,9 @@ enum ScrapeType {
"Type of the content a scraper generates"
enum ScrapeContentType {
GALLERY
IMAGE
MOVIE
GROUP
PERFORMER
SCENE
}
@@ -21,7 +23,9 @@ union ScrapedContent =
| ScrapedTag
| ScrapedScene
| ScrapedGallery
| ScrapedImage
| ScrapedMovie
| ScrapedGroup
| ScrapedPerformer
type ScraperSpec {
@@ -39,17 +43,26 @@ type Scraper {
scene: ScraperSpec
"Details for gallery scraper"
gallery: ScraperSpec
"Details for image scraper"
image: ScraperSpec
"Details for movie scraper"
movie: ScraperSpec
movie: ScraperSpec @deprecated(reason: "use group")
"Details for group scraper"
group: ScraperSpec
}
type ScrapedStudio {
"Set if studio matched"
stored_id: ID
name: String!
url: String
url: String @deprecated(reason: "use urls")
urls: [String!]
parent: ScrapedStudio
image: String
details: String
"Aliases must be comma-delimited to be parsed correctly"
aliases: String
tags: [ScrapedTag!]
remote_site_id: String
}
@@ -58,6 +71,8 @@ type ScrapedTag {
"Set if tag matched"
stored_id: ID
name: String!
"Remote site ID, if applicable"
remote_site_id: String
}
type ScrapedScene {
@@ -76,7 +91,8 @@ type ScrapedScene {
studio: ScrapedStudio
tags: [ScrapedTag!]
performers: [ScrapedPerformer!]
movies: [ScrapedMovie!]
movies: [ScrapedMovie!] @deprecated(reason: "use groups")
groups: [ScrapedGroup!]
remote_site_id: String
duration: Int
@@ -99,7 +115,9 @@ input ScrapedSceneInput {
type ScrapedGallery {
title: String
code: String
details: String
photographer: String
url: String @deprecated(reason: "use urls")
urls: [String!]
date: String
@@ -111,7 +129,9 @@ type ScrapedGallery {
input ScrapedGalleryInput {
title: String
code: String
details: String
photographer: String
url: String @deprecated(reason: "use urls")
urls: [String!]
date: String
@@ -119,12 +139,32 @@ input ScrapedGalleryInput {
# no studio, tags or performers
}
type ScrapedImage {
title: String
code: String
details: String
photographer: String
urls: [String!]
date: String
studio: ScrapedStudio
tags: [ScrapedTag!]
performers: [ScrapedPerformer!]
}
input ScrapedImageInput {
title: String
code: String
details: String
urls: [String!]
date: String
}
input ScraperSourceInput {
"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 to scrape with. Should be unset if stash_box_endpoint/stash_box_index is set"
scraper_id: ID
}
@@ -133,7 +173,7 @@ type ScraperSource {
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 to scrape with. Should be unset if stash_box_endpoint/stash_box_index is set"
scraper_id: ID
}
@@ -158,6 +198,13 @@ input ScrapeSingleStudioInput {
query: String
}
input ScrapeSingleTagInput {
"""
Query can be either a name or a Stash ID
"""
query: String
}
input ScrapeSinglePerformerInput {
"Instructs to query by string"
query: String
@@ -181,18 +228,38 @@ input ScrapeSingleGalleryInput {
gallery_input: ScrapedGalleryInput
}
input ScrapeSingleImageInput {
"Instructs to query by string"
query: String
"Instructs to query by image id"
image_id: ID
"Instructs to query by image fragment"
image_input: ScrapedImageInput
}
input ScrapeSingleMovieInput {
"Instructs to query by string"
query: String
"Instructs to query by movie id"
movie_id: ID
"Instructs to query by gallery fragment"
"Instructs to query by movie fragment"
movie_input: ScrapedMovieInput
}
input ScrapeSingleGroupInput {
"Instructs to query by string"
query: String
"Instructs to query by group id"
group_id: ID
"Instructs to query by group fragment"
group_input: ScrapedGroupInput
}
input StashBoxSceneQueryInput {
"Index of the configured stash-box instance to use"
stash_box_index: Int!
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
"Endpoint of the stash-box instance to use"
stash_box_endpoint: String
"Instructs query by scene fingerprints"
scene_ids: [ID!]
"Query by query string"
@@ -201,7 +268,9 @@ input StashBoxSceneQueryInput {
input StashBoxPerformerQueryInput {
"Index of the configured stash-box instance to use"
stash_box_index: Int!
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
"Endpoint of the stash-box instance to use"
stash_box_endpoint: String
"Instructs query by scene fingerprints"
performer_ids: [ID!]
"Query by query string"
@@ -219,22 +288,32 @@ type StashBoxFingerprint {
duration: Int!
}
"If neither ids nor names are set, tag all items"
"""
Accepts either ids, or a combination of names and stash_ids.
If none are set, then all existing items will be tagged.
"""
input StashBoxBatchTagInput {
"Stash endpoint to use for the tagging"
endpoint: Int!
endpoint: Int @deprecated(reason: "use stash_box_endpoint")
"Endpoint of the stash-box instance to use"
stash_box_endpoint: String
"Fields to exclude when executing the tagging"
exclude_fields: [String!]
"Refresh items already tagged by StashBox if true. Only tag items with no StashBox tagging if false"
refresh: Boolean!
"If batch adding studios, should their parent studios also be created?"
createParent: Boolean!
"If set, only tag these ids"
"""
IDs in stash of the items to update.
If set, names and stash_ids fields will be ignored.
"""
ids: [ID!]
"If set, only tag these names"
"Names of the items in the stash-box instance to search for and create"
names: [String!]
"If set, only tag these performer ids"
"Stash IDs of the items in the stash-box instance to search for and create"
stash_ids: [String!]
"IDs in stash of the performers to update"
performer_ids: [ID!] @deprecated(reason: "use ids")
"If set, only tag these performer names"
"Names of the performers in the stash-box instance to search for and create"
performer_names: [String!] @deprecated(reason: "use names")
}

View File

@@ -2,30 +2,37 @@ type StashBox {
endpoint: String!
api_key: String!
name: String!
max_requests_per_minute: Int!
}
input StashBoxInput {
endpoint: String!
api_key: String!
name: String!
# defaults to 240
max_requests_per_minute: Int
}
type StashID {
endpoint: String!
stash_id: String!
updated_at: Time!
}
input StashIDInput {
endpoint: String!
stash_id: String!
updated_at: Time
}
input StashBoxFingerprintSubmissionInput {
scene_ids: [String!]!
stash_box_index: Int!
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
stash_box_endpoint: String
}
input StashBoxDraftSubmissionInput {
id: String!
stash_box_index: Int!
stash_box_index: Int @deprecated(reason: "use stash_box_endpoint")
stash_box_endpoint: String
}

View File

@@ -7,7 +7,8 @@ type StatsResultType {
gallery_count: Int!
performer_count: Int!
studio_count: Int!
movie_count: Int!
group_count: Int!
movie_count: Int! @deprecated(reason: "use group_count instead")
tag_count: Int!
total_o_count: Int!
total_play_duration: Float!

View File

@@ -1,11 +1,12 @@
type Studio {
id: ID!
name: String!
checksum: String! @deprecated(reason: "MD5 hash of name, use name directly")
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]!
parent_studio: Studio
child_studios: [Studio!]!
aliases: [String!]!
tags: [Tag!]!
ignore_auto_tag: Boolean!
image_path: String # Resolver
@@ -13,48 +14,65 @@ type Studio {
image_count(depth: Int): Int! # Resolver
gallery_count(depth: Int): Int! # Resolver
performer_count(depth: Int): Int! # Resolver
movie_count(depth: Int): Int! # Resolver
group_count(depth: Int): Int! # Resolver
movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver
stash_ids: [StashID!]!
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
favorite: Boolean!
details: String
created_at: Time!
updated_at: Time!
movies: [Movie!]!
groups: [Group!]!
movies: [Movie!]! @deprecated(reason: "use groups instead")
o_counter: Int
}
input StudioCreateInput {
name: String!
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
parent_id: ID
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
favorite: Boolean
details: String
aliases: [String!]
tag_ids: [ID!]
ignore_auto_tag: Boolean
}
input StudioUpdateInput {
id: ID!
name: String
url: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
parent_id: ID
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
favorite: Boolean
details: String
aliases: [String!]
tag_ids: [ID!]
ignore_auto_tag: Boolean
}
input BulkStudioUpdateInput {
ids: [ID!]!
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
parent_id: ID
# rating expressed as 1-100
rating100: Int
favorite: Boolean
details: String
tag_ids: BulkUpdateIds
ignore_auto_tag: Boolean
}

View File

@@ -1,18 +1,24 @@
type Tag {
id: ID!
name: String!
"Value that does not appear in the UI but overrides name for sorting"
sort_name: String
description: String
aliases: [String!]!
ignore_auto_tag: Boolean!
created_at: Time!
updated_at: Time!
favorite: Boolean!
stash_ids: [StashID!]!
image_path: String # Resolver
scene_count(depth: Int): Int! # Resolver
scene_marker_count(depth: Int): Int! # Resolver
image_count(depth: Int): Int! # Resolver
gallery_count(depth: Int): Int! # Resolver
performer_count(depth: Int): Int! # Resolver
studio_count(depth: Int): Int! # Resolver
group_count(depth: Int): Int! # Resolver
movie_count(depth: Int): Int! @deprecated(reason: "use group_count instead") # Resolver
parents: [Tag!]!
children: [Tag!]!
@@ -22,12 +28,15 @@ type Tag {
input TagCreateInput {
name: String!
"Value that does not appear in the UI but overrides name for sorting"
sort_name: String
description: String
aliases: [String!]
ignore_auto_tag: Boolean
favorite: Boolean
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
parent_ids: [ID!]
child_ids: [ID!]
@@ -36,12 +45,15 @@ input TagCreateInput {
input TagUpdateInput {
id: ID!
name: String
"Value that does not appear in the UI but overrides name for sorting"
sort_name: String
description: String
aliases: [String!]
ignore_auto_tag: Boolean
favorite: Boolean
"This should be a URL or a base64 encoded data URL"
image: String
stash_ids: [StashIDInput!]
parent_ids: [ID!]
child_ids: [ID!]
@@ -60,3 +72,14 @@ input TagsMergeInput {
source: [ID!]!
destination: ID!
}
input BulkTagUpdateInput {
ids: [ID!]
description: String
aliases: BulkUpdateStrings
ignore_auto_tag: Boolean
favorite: Boolean
parent_ids: BulkUpdateIds
child_ids: BulkUpdateIds
}

View File

@@ -13,6 +13,7 @@ fragment ImageFragment on Image {
fragment StudioFragment on Studio {
name
id
aliases
urls {
...URLFragment
}
@@ -30,11 +31,6 @@ fragment TagFragment on Tag {
id
}
fragment FuzzyDateFragment on FuzzyDate {
date
accuracy
}
fragment MeasurementsFragment on Measurements {
band_size
cup_size
@@ -54,15 +50,16 @@ fragment PerformerFragment on Performer {
aliases
gender
merged_ids
deleted
merged_into_id
urls {
...URLFragment
}
images {
...ImageFragment
}
birthdate {
...FuzzyDateFragment
}
birth_date
death_date
ethnicity
country
eye_color
@@ -173,6 +170,21 @@ query FindStudio($id: ID, $name: String) {
}
}
query FindTag($id: ID, $name: String) {
findTag(id: $id, name: $name) {
...TagFragment
}
}
query QueryTags($input: TagQueryInput!) {
queryTags(input: $input) {
count
tags {
...TagFragment
}
}
}
mutation SubmitFingerprint($input: FingerprintSubmission!) {
submitFingerprint(input: $input)
}

View File

@@ -16,12 +16,12 @@ import (
const (
tripwireActivatedErrMsg = "Stash is exposed to the public internet without authentication, and is not serving any more content to protect your privacy. " +
"More information and fixes are available at https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet"
"More information and fixes are available at https://discourse.stashapp.cc/t/-/1658"
externalAccessErrMsg = "You have attempted to access Stash over the internet, and authentication is not enabled. " +
"This is extremely dangerous! The whole world can see your your stash page and browse your files! " +
"Stash is not answering any other requests to protect your privacy. " +
"Please read the log entry or visit https://docs.stashapp.cc/networking/authentication-required-when-accessing-stash-from-the-internet"
"Please read the log entry or visit https://discourse.stashapp.cc/t/-/1658"
)
func allowUnauthenticated(r *http.Request) bool {
@@ -42,7 +42,7 @@ func authenticateHandler() func(http.Handler) http.Handler {
userID, err := manager.GetInstance().SessionStore.Authenticate(w, r)
if err != nil {
if errors.Is(err, session.ErrUnauthorized) {
if !errors.Is(err, session.ErrUnauthorized) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

38
internal/api/bool_map.go Normal file
View File

@@ -0,0 +1,38 @@
package api
import (
"encoding/json"
"fmt"
"io"
"github.com/99designs/gqlgen/graphql"
)
func MarshalBoolMap(val map[string]bool) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
panic(err)
}
})
}
func UnmarshalBoolMap(v interface{}) (map[string]bool, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%T is not a map", v)
}
result := make(map[string]bool)
for k, v := range m {
key := k
val, ok := v.(bool)
if !ok {
return nil, fmt.Errorf("key %s (%T) is not a bool", k, v)
}
result[key] = val
}
return result, nil
}

View File

@@ -98,7 +98,7 @@ func (t changesetTranslator) string(value *string) string {
return ""
}
return *value
return strings.TrimSpace(*value)
}
func (t changesetTranslator) optionalString(value *string, field string) models.OptionalString {
@@ -106,7 +106,12 @@ func (t changesetTranslator) optionalString(value *string, field string) models.
return models.OptionalString{}
}
return models.NewOptionalStringPtr(value)
if value == nil {
return models.NewOptionalStringPtr(nil)
}
trimmed := strings.TrimSpace(*value)
return models.NewOptionalString(trimmed)
}
func (t changesetTranslator) optionalDate(value *string, field string) (models.OptionalDate, error) {
@@ -153,40 +158,6 @@ func (t changesetTranslator) intPtrFromString(value *string) (*int, error) {
return &vv, nil
}
func (t changesetTranslator) ratingConversion(legacyValue *int, rating100Value *int) *int {
const (
legacyField = "rating"
rating100Field = "rating100"
)
legacyRating := t.optionalInt(legacyValue, legacyField)
if legacyRating.Set && !legacyRating.Null {
ret := models.Rating5To100(legacyRating.Value)
return &ret
}
o := t.optionalInt(rating100Value, rating100Field)
if o.Set && !o.Null {
return &o.Value
}
return nil
}
func (t changesetTranslator) optionalRatingConversion(legacyValue *int, rating100Value *int) models.OptionalInt {
const (
legacyField = "rating"
rating100Field = "rating100"
)
legacyRating := t.optionalInt(legacyValue, legacyField)
if legacyRating.Set && !legacyRating.Null {
legacyRating.Value = models.Rating5To100(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{}
@@ -352,8 +323,14 @@ func (t changesetTranslator) updateStrings(value []string, field string) *models
return nil
}
// Trim whitespace from each string
trimmedValues := make([]string, len(value))
for i, v := range value {
trimmedValues[i] = strings.TrimSpace(v)
}
return &models.UpdateStrings{
Values: value,
Values: trimmedValues,
Mode: models.RelationshipUpdateModeSet,
}
}
@@ -363,49 +340,98 @@ func (t changesetTranslator) updateStringsBulk(value *BulkUpdateStrings, field s
return nil
}
// Trim whitespace from each string
trimmedValues := make([]string, len(value.Values))
for i, v := range value.Values {
trimmedValues[i] = strings.TrimSpace(v)
}
return &models.UpdateStrings{
Values: value.Values,
Values: trimmedValues,
Mode: value.Mode,
}
}
func (t changesetTranslator) updateStashIDs(value []models.StashID, field string) *models.UpdateStashIDs {
func (t changesetTranslator) updateStashIDs(value models.StashIDInputs, field string) *models.UpdateStashIDs {
if !t.hasField(field) {
return nil
}
return &models.UpdateStashIDs{
StashIDs: value,
StashIDs: value.ToStashIDs(),
Mode: models.RelationshipUpdateModeSet,
}
}
func (t changesetTranslator) relatedMovies(value []models.SceneMovieInput) (models.RelatedMovies, error) {
moviesScenes, err := models.MoviesScenesFromInput(value)
func (t changesetTranslator) relatedGroupsFromMovies(value []models.SceneMovieInput) (models.RelatedGroups, error) {
groupsScenes, err := models.GroupsScenesFromInput(value)
if err != nil {
return models.RelatedMovies{}, err
return models.RelatedGroups{}, err
}
return models.NewRelatedMovies(moviesScenes), nil
return models.NewRelatedGroups(groupsScenes), nil
}
func (t changesetTranslator) updateMovieIDs(value []models.SceneMovieInput, field string) (*models.UpdateMovieIDs, error) {
func groupsScenesFromGroupInput(input []models.SceneGroupInput) ([]models.GroupsScenes, error) {
ret := make([]models.GroupsScenes, len(input))
for i, v := range input {
mID, err := strconv.Atoi(v.GroupID)
if err != nil {
return nil, fmt.Errorf("invalid group ID: %s", v.GroupID)
}
ret[i] = models.GroupsScenes{
GroupID: mID,
SceneIndex: v.SceneIndex,
}
}
return ret, nil
}
func (t changesetTranslator) relatedGroups(value []models.SceneGroupInput) (models.RelatedGroups, error) {
groupsScenes, err := groupsScenesFromGroupInput(value)
if err != nil {
return models.RelatedGroups{}, err
}
return models.NewRelatedGroups(groupsScenes), nil
}
func (t changesetTranslator) updateGroupIDsFromMovies(value []models.SceneMovieInput, field string) (*models.UpdateGroupIDs, error) {
if !t.hasField(field) {
return nil, nil
}
moviesScenes, err := models.MoviesScenesFromInput(value)
groupsScenes, err := models.GroupsScenesFromInput(value)
if err != nil {
return nil, err
}
return &models.UpdateMovieIDs{
Movies: moviesScenes,
return &models.UpdateGroupIDs{
Groups: groupsScenes,
Mode: models.RelationshipUpdateModeSet,
}, nil
}
func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateMovieIDs, error) {
func (t changesetTranslator) updateGroupIDs(value []models.SceneGroupInput, field string) (*models.UpdateGroupIDs, error) {
if !t.hasField(field) {
return nil, nil
}
groupsScenes, err := groupsScenesFromGroupInput(value)
if err != nil {
return nil, err
}
return &models.UpdateGroupIDs{
Groups: groupsScenes,
Mode: models.RelationshipUpdateModeSet,
}, nil
}
func (t changesetTranslator) updateGroupIDsBulk(value *BulkUpdateIds, field string) (*models.UpdateGroupIDs, error) {
if !t.hasField(field) || value == nil {
return nil, nil
}
@@ -415,13 +441,74 @@ func (t changesetTranslator) updateMovieIDsBulk(value *BulkUpdateIds, field stri
return nil, fmt.Errorf("converting ids [%v]: %w", value.Ids, err)
}
movies := make([]models.MoviesScenes, len(ids))
groups := make([]models.GroupsScenes, len(ids))
for i, id := range ids {
movies[i] = models.MoviesScenes{MovieID: id}
groups[i] = models.GroupsScenes{GroupID: id}
}
return &models.UpdateMovieIDs{
Movies: movies,
return &models.UpdateGroupIDs{
Groups: groups,
Mode: value.Mode,
}, nil
}
func groupsDescriptionsFromGroupInput(input []*GroupDescriptionInput) ([]models.GroupIDDescription, error) {
ret := make([]models.GroupIDDescription, len(input))
for i, v := range input {
gID, err := strconv.Atoi(v.GroupID)
if err != nil {
return nil, fmt.Errorf("invalid group ID: %s", v.GroupID)
}
ret[i] = models.GroupIDDescription{
GroupID: gID,
}
if v.Description != nil {
ret[i].Description = strings.TrimSpace(*v.Description)
}
}
return ret, nil
}
func (t changesetTranslator) groupIDDescriptions(value []*GroupDescriptionInput) (models.RelatedGroupDescriptions, error) {
groupsScenes, err := groupsDescriptionsFromGroupInput(value)
if err != nil {
return models.RelatedGroupDescriptions{}, err
}
return models.NewRelatedGroupDescriptions(groupsScenes), nil
}
func (t changesetTranslator) updateGroupIDDescriptions(value []*GroupDescriptionInput, field string) (*models.UpdateGroupDescriptions, error) {
if !t.hasField(field) {
return nil, nil
}
groupsScenes, err := groupsDescriptionsFromGroupInput(value)
if err != nil {
return nil, err
}
return &models.UpdateGroupDescriptions{
Groups: groupsScenes,
Mode: models.RelationshipUpdateModeSet,
}, nil
}
func (t changesetTranslator) updateGroupIDDescriptionsBulk(value *BulkUpdateGroupDescriptionsInput, field string) (*models.UpdateGroupDescriptions, error) {
if !t.hasField(field) || value == nil {
return nil, nil
}
groups, err := groupsDescriptionsFromGroupInput(value.Groups)
if err != nil {
return nil, err
}
return &models.UpdateGroupDescriptions{
Groups: groups,
Mode: value.Mode,
}, nil
}

View File

@@ -7,8 +7,10 @@ import (
"fmt"
"io"
"net/http"
"os"
"regexp"
"runtime"
"strings"
"time"
"golang.org/x/sys/cpu"
@@ -26,8 +28,8 @@ const defaultSHLength int = 8 // default length of SHA short hash returned by <g
var stashReleases = func() map[string]string {
return map[string]string{
"darwin/amd64": "stash-osx",
"darwin/arm64": "stash-osx-applesilicon",
"darwin/amd64": "stash-macos",
"darwin/arm64": "stash-macos",
"linux/amd64": "stash-linux",
"windows/amd64": "stash-win.exe",
"linux/arm": "stash-linux-arm32v6",
@@ -36,6 +38,24 @@ var stashReleases = func() map[string]string {
}
}
// isMacOSBundle checks if the application is running from within a macOS .app bundle
func isMacOSBundle() bool {
exec, err := os.Executable()
return err == nil && strings.Contains(exec, "Stash.app/")
}
// getWantedRelease determines which release variant to download based on platform and bundle type
func getWantedRelease(platform string) string {
release := stashReleases()[platform]
// On macOS, check if running from .app bundle
if runtime.GOOS == "darwin" && isMacOSBundle() {
return "Stash.app.zip"
}
return release
}
type githubReleasesResponse struct {
Url string
Assets_url string
@@ -168,7 +188,7 @@ func GetLatestRelease(ctx context.Context) (*LatestRelease, error) {
}
platform := fmt.Sprintf("%s/%s", runtime.GOOS, arch)
wantedRelease := stashReleases()[platform]
wantedRelease := getWantedRelease(platform)
url := apiReleases
if build.IsDevelop() {

View File

@@ -5,12 +5,13 @@ package api
type key int
const (
// galleryKey key = 0
performerKey key = iota + 1
galleryKey key = 0
performerKey
sceneKey
studioKey
movieKey
groupKey
tagKey
downloadKey
imageKey
pluginKey
)

View File

@@ -0,0 +1,12 @@
package api
import "github.com/stashapp/stash/pkg/models"
func handleUpdateCustomFields(input models.CustomFieldsInput) models.CustomFieldsInput {
ret := input
// convert json.Numbers to int/float
ret.Full = convertMapJSONNumbers(ret.Full)
ret.Partial = convertMapJSONNumbers(ret.Partial)
return ret
}

View File

@@ -4,6 +4,7 @@ import (
"io/fs"
"os"
"path/filepath"
"strings"
"golang.org/x/text/collate"
)
@@ -25,13 +26,27 @@ func (s dirLister) Bytes(i int) []byte {
// 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
dirPath := path
files, err := os.ReadDir(path)
if err != nil {
path = filepath.Dir(path)
files, err = os.ReadDir(path)
dirPath = filepath.Dir(path)
dirFiles, err := os.ReadDir(dirPath)
if err != nil {
return dirPaths, err
}
// Filter dir contents by last path fragment if the dir isn't an exact match
base := strings.ToLower(filepath.Base(path))
if base != "." && base != string(filepath.Separator) {
for _, file := range dirFiles {
if strings.HasPrefix(strings.ToLower(file.Name()), base) {
files = append(files, file)
}
}
} else {
files = dirFiles
}
}
if col != nil {
@@ -42,7 +57,7 @@ func listDir(col *collate.Collator, path string) ([]string, error) {
if !file.IsDir() {
continue
}
dirPaths = append(dirPaths, filepath.Join(path, file.Name()))
dirPaths = append(dirPaths, filepath.Join(dirPath, file.Name()))
}
return dirPaths, nil
}

2
internal/api/doc.go Normal file
View File

@@ -0,0 +1,2 @@
// Package api provides the HTTP and Graphql API for the application.
package api

23
internal/api/fields.go Normal file
View File

@@ -0,0 +1,23 @@
package api
import (
"context"
"github.com/99designs/gqlgen/graphql"
)
type queryFields []string
func collectQueryFields(ctx context.Context) queryFields {
fields := graphql.CollectAllFields(ctx)
return queryFields(fields)
}
func (f queryFields) Has(field string) bool {
for _, v := range f {
if v == field {
return true
}
}
return false
}

View File

@@ -1,12 +1,13 @@
package api
import (
"errors"
"fmt"
"io"
"io/fs"
"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"
@@ -18,13 +19,14 @@ type imageBox struct {
files []string
}
var imageExtensions = []string{
var imageBoxExts = []string{
".jpg",
".jpeg",
".png",
".gif",
".svg",
".webp",
".avif",
}
func newImageBox(box fs.FS) (*imageBox, error) {
@@ -42,7 +44,7 @@ func newImageBox(box fs.FS) (*imageBox, error) {
}
baseName := strings.ToLower(d.Name())
for _, ext := range imageExtensions {
for _, ext := range imageBoxExts {
if strings.HasSuffix(baseName, ext) {
ret.files = append(ret.files, path)
break
@@ -55,65 +57,14 @@ func newImageBox(box fs.FS) (*imageBox, error) {
return ret, err
}
var performerBox *imageBox
var performerBoxMale *imageBox
var performerBoxCustom *imageBox
func initialiseImages() {
var err error
performerBox, err = newImageBox(&static.Performer)
if err != nil {
logger.Warnf("error loading performer images: %v", err)
}
performerBoxMale, err = newImageBox(&static.PerformerMale)
if err != nil {
logger.Warnf("error loading male performer images: %v", err)
}
initialiseCustomImages()
}
func initialiseCustomImages() {
customPath := config.GetInstance().GetCustomPerformerImageLocation()
if customPath != "" {
logger.Debugf("Loading custom performer images from %s", customPath)
// We need to set performerBoxCustom at runtime, as this is a custom path, and store it in a pointer.
var err error
performerBoxCustom, err = newImageBox(os.DirFS(customPath))
if err != nil {
logger.Warnf("error loading custom performer from %s: %v", customPath, err)
}
} else {
performerBoxCustom = nil
}
}
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.
if performerBoxCustom != nil && len(performerBoxCustom.files) > 0 {
box = performerBoxCustom
func (box *imageBox) GetRandomImageByName(name string) ([]byte, error) {
files := box.files
if len(files) == 0 {
return nil, errors.New("box is empty")
}
var g models.GenderEnum
if gender != nil {
g = *gender
}
if box == nil {
switch g {
case models.GenderEnumFemale, models.GenderEnumTransgenderFemale:
box = performerBox
case models.GenderEnumMale, models.GenderEnumTransgenderMale:
box = performerBoxMale
default:
box = performerBox
}
}
imageFiles := box.files
index := hash.IntFromString(name) % uint64(len(imageFiles))
img, err := box.box.Open(imageFiles[index])
index := hash.IntFromString(name) % uint64(len(files))
img, err := box.box.Open(files[index])
if err != nil {
return nil, err
}
@@ -121,3 +72,68 @@ func getRandomPerformerImageUsingName(name string, gender *models.GenderEnum, cu
return io.ReadAll(img)
}
var performerBox *imageBox
var performerBoxMale *imageBox
var performerBoxCustom *imageBox
func init() {
var err error
performerBox, err = newImageBox(static.Sub(static.Performer))
if err != nil {
panic(fmt.Sprintf("loading performer images: %v", err))
}
performerBoxMale, err = newImageBox(static.Sub(static.PerformerMale))
if err != nil {
panic(fmt.Sprintf("loading male performer images: %v", err))
}
}
func initCustomPerformerImages(customPath string) {
if customPath != "" {
logger.Debugf("Loading custom performer images from %s", customPath)
var err error
performerBoxCustom, err = newImageBox(os.DirFS(customPath))
if err != nil {
logger.Warnf("error loading custom performer images from %s: %v", customPath, err)
}
} else {
performerBoxCustom = nil
}
}
func getDefaultPerformerImage(name string, gender *models.GenderEnum, sfwMode bool) []byte {
// try the custom box first if we have one
if performerBoxCustom != nil {
ret, err := performerBoxCustom.GetRandomImageByName(name)
if err == nil {
return ret
}
logger.Warnf("error loading custom default performer image: %v", err)
}
if sfwMode {
return static.ReadAll(static.DefaultSFWPerformerImage)
}
var g models.GenderEnum
if gender != nil {
g = *gender
}
var box *imageBox
switch g {
case models.GenderEnumFemale, models.GenderEnumTransgenderFemale:
box = performerBox
case models.GenderEnumMale, models.GenderEnumTransgenderMale:
box = performerBoxMale
default:
box = performerBox
}
ret, err := box.GetRandomImageByName(name)
if err != nil {
logger.Warnf("error loading default performer image: %v", err)
}
return ret
}

65
internal/api/json.go Normal file
View File

@@ -0,0 +1,65 @@
package api
import (
"encoding/json"
"strings"
"github.com/stashapp/stash/pkg/models"
)
// jsonNumberToNumber converts a JSON number to either a float64 or int64.
func jsonNumberToNumber(n json.Number) interface{} {
if strings.Contains(string(n), ".") {
f, _ := n.Float64()
return f
}
ret, _ := n.Int64()
return ret
}
// anyJSONNumberToNumber converts a JSON number using jsonNumberToNumber, otherwise it returns the existing value
func anyJSONNumberToNumber(v any) any {
if n, ok := v.(json.Number); ok {
return jsonNumberToNumber(n)
}
return v
}
// ConvertMapJSONNumbers converts all JSON numbers in a map to either float64 or int64.
func convertMapJSONNumbers(m map[string]interface{}) (ret map[string]interface{}) {
if m == nil {
return nil
}
ret = make(map[string]interface{})
for k, v := range m {
if n, ok := v.(json.Number); ok {
ret[k] = jsonNumberToNumber(n)
} else if mm, ok := v.(map[string]interface{}); ok {
ret[k] = convertMapJSONNumbers(mm)
} else {
ret[k] = v
}
}
return ret
}
func convertCustomFieldCriterionValues(c models.CustomFieldCriterionInput) models.CustomFieldCriterionInput {
nv := make([]any, len(c.Value))
for i, v := range c.Value {
nv[i] = anyJSONNumberToNumber(v)
}
c.Value = nv
return c
}
func convertCustomFieldCriterionInputJSONNumbers(c []models.CustomFieldCriterionInput) []models.CustomFieldCriterionInput {
ret := make([]models.CustomFieldCriterionInput, len(c))
for i, v := range c {
ret[i] = convertCustomFieldCriterionValues(v)
}
return ret
}

60
internal/api/json_test.go Normal file
View File

@@ -0,0 +1,60 @@
package api
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
)
func TestConvertMapJSONNumbers(t *testing.T) {
tests := []struct {
name string
input map[string]interface{}
expected map[string]interface{}
}{
{
name: "Convert JSON numbers to numbers",
input: map[string]interface{}{
"int": json.Number("12"),
"float": json.Number("12.34"),
"string": "foo",
},
expected: map[string]interface{}{
"int": int64(12),
"float": 12.34,
"string": "foo",
},
},
{
name: "Convert JSON numbers to numbers in nested maps",
input: map[string]interface{}{
"foo": map[string]interface{}{
"int": json.Number("56"),
"float": json.Number("56.78"),
"nested-string": "bar",
},
"int": json.Number("12"),
"float": json.Number("12.34"),
"string": "foo",
},
expected: map[string]interface{}{
"foo": map[string]interface{}{
"int": int64(56),
"float": 56.78,
"nested-string": "bar",
},
"int": int64(12),
"float": 12.34,
"string": "foo",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := convertMapJSONNumbers(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}

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/models"
)
// CustomFieldsLoaderConfig captures the config to create a new CustomFieldsLoader
type CustomFieldsLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]models.CustomFieldMap, []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
}
// NewCustomFieldsLoader creates a new CustomFieldsLoader given a fetch, wait, and maxBatch
func NewCustomFieldsLoader(config CustomFieldsLoaderConfig) *CustomFieldsLoader {
return &CustomFieldsLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// CustomFieldsLoader batches and caches requests
type CustomFieldsLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]models.CustomFieldMap, []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.CustomFieldMap
// 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 *customFieldsLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type customFieldsLoaderBatch struct {
keys []int
data []models.CustomFieldMap
error []error
closing bool
done chan struct{}
}
// Load a CustomFieldMap by key, batching and caching will be applied automatically
func (l *CustomFieldsLoader) Load(key int) (models.CustomFieldMap, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a CustomFieldMap.
// 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 *CustomFieldsLoader) LoadThunk(key int) func() (models.CustomFieldMap, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (models.CustomFieldMap, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &customFieldsLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (models.CustomFieldMap, error) {
<-batch.done
var data models.CustomFieldMap
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 *CustomFieldsLoader) LoadAll(keys []int) ([]models.CustomFieldMap, []error) {
results := make([]func() (models.CustomFieldMap, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
customFieldMaps := make([]models.CustomFieldMap, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
customFieldMaps[i], errors[i] = thunk()
}
return customFieldMaps, errors
}
// LoadAllThunk returns a function that when called will block waiting for a CustomFieldMaps.
// 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 *CustomFieldsLoader) LoadAllThunk(keys []int) func() ([]models.CustomFieldMap, []error) {
results := make([]func() (models.CustomFieldMap, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]models.CustomFieldMap, []error) {
customFieldMaps := make([]models.CustomFieldMap, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
customFieldMaps[i], errors[i] = thunk()
}
return customFieldMaps, 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 *CustomFieldsLoader) Prime(key int, value models.CustomFieldMap) 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 *CustomFieldsLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *CustomFieldsLoader) unsafeSet(key int, value models.CustomFieldMap) {
if l.cache == nil {
l.cache = map[int]models.CustomFieldMap{}
}
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 *customFieldsLoaderBatch) keyIndex(l *CustomFieldsLoader, 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 *customFieldsLoaderBatch) startTimer(l *CustomFieldsLoader) {
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 *customFieldsLoaderBatch) end(l *CustomFieldsLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -1,15 +1,25 @@
// Package loaders contains the dataloaders used by the resolver in [api].
// They are generated with `make generate-dataloaders`.
// The dataloaders are used to batch requests to the database.
//go:generate go run github.com/vektah/dataloaden SceneLoader int *github.com/stashapp/stash/pkg/models.Scene
//go:generate go run github.com/vektah/dataloaden GalleryLoader int *github.com/stashapp/stash/pkg/models.Gallery
//go:generate go run github.com/vektah/dataloaden ImageLoader int *github.com/stashapp/stash/pkg/models.Image
//go:generate go run github.com/vektah/dataloaden PerformerLoader int *github.com/stashapp/stash/pkg/models.Performer
//go:generate go run github.com/vektah/dataloaden StudioLoader int *github.com/stashapp/stash/pkg/models.Studio
//go:generate go run github.com/vektah/dataloaden TagLoader int *github.com/stashapp/stash/pkg/models.Tag
//go:generate go run github.com/vektah/dataloaden MovieLoader int *github.com/stashapp/stash/pkg/models.Movie
//go:generate go run github.com/vektah/dataloaden GroupLoader int *github.com/stashapp/stash/pkg/models.Group
//go:generate go run github.com/vektah/dataloaden FileLoader github.com/stashapp/stash/pkg/models.FileID github.com/stashapp/stash/pkg/models.File
//go:generate go run github.com/vektah/dataloaden FolderLoader github.com/stashapp/stash/pkg/models.FolderID *github.com/stashapp/stash/pkg/models.Folder
//go:generate go run github.com/vektah/dataloaden SceneFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
//go:generate go run github.com/vektah/dataloaden ImageFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
//go:generate go run github.com/vektah/dataloaden GalleryFileIDsLoader int []github.com/stashapp/stash/pkg/models.FileID
//go:generate go run github.com/vektah/dataloaden CustomFieldsLoader int github.com/stashapp/stash/pkg/models.CustomFieldMap
//go:generate go run github.com/vektah/dataloaden SceneOCountLoader int int
//go:generate go run github.com/vektah/dataloaden ScenePlayCountLoader int int
//go:generate go run github.com/vektah/dataloaden SceneOHistoryLoader int []time.Time
//go:generate go run github.com/vektah/dataloaden ScenePlayHistoryLoader int []time.Time
//go:generate go run github.com/vektah/dataloaden SceneLastPlayedLoader int *time.Time
package loaders
import (
@@ -17,9 +27,7 @@ import (
"net/http"
"time"
"github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/txn"
)
type contextKey struct{ name string }
@@ -34,23 +42,32 @@ const (
)
type Loaders struct {
SceneByID *SceneLoader
SceneFiles *SceneFileIDsLoader
SceneByID *SceneLoader
SceneFiles *SceneFileIDsLoader
ScenePlayCount *ScenePlayCountLoader
SceneOCount *SceneOCountLoader
ScenePlayHistory *ScenePlayHistoryLoader
SceneOHistory *SceneOHistoryLoader
SceneLastPlayed *SceneLastPlayedLoader
ImageFiles *ImageFileIDsLoader
GalleryFiles *GalleryFileIDsLoader
GalleryByID *GalleryLoader
ImageByID *ImageLoader
PerformerByID *PerformerLoader
StudioByID *StudioLoader
TagByID *TagLoader
MovieByID *MovieLoader
FileByID *FileLoader
GalleryByID *GalleryLoader
ImageByID *ImageLoader
PerformerByID *PerformerLoader
PerformerCustomFields *CustomFieldsLoader
StudioByID *StudioLoader
TagByID *TagLoader
GroupByID *GroupLoader
FileByID *FileLoader
FolderByID *FolderLoader
}
type Middleware struct {
DatabaseProvider txn.DatabaseProvider
Repository manager.Repository
Repository models.Repository
}
func (m Middleware) Middleware(next http.Handler) http.Handler {
@@ -77,6 +94,11 @@ func (m Middleware) Middleware(next http.Handler) http.Handler {
maxBatch: maxBatch,
fetch: m.fetchPerformers(ctx),
},
PerformerCustomFields: &CustomFieldsLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchPerformerCustomFields(ctx),
},
StudioByID: &StudioLoader{
wait: wait,
maxBatch: maxBatch,
@@ -87,16 +109,21 @@ func (m Middleware) Middleware(next http.Handler) http.Handler {
maxBatch: maxBatch,
fetch: m.fetchTags(ctx),
},
MovieByID: &MovieLoader{
GroupByID: &GroupLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchMovies(ctx),
fetch: m.fetchGroups(ctx),
},
FileByID: &FileLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchFiles(ctx),
},
FolderByID: &FolderLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchFolders(ctx),
},
SceneFiles: &SceneFileIDsLoader{
wait: wait,
maxBatch: maxBatch,
@@ -112,6 +139,31 @@ func (m Middleware) Middleware(next http.Handler) http.Handler {
maxBatch: maxBatch,
fetch: m.fetchGalleriesFileIDs(ctx),
},
ScenePlayCount: &ScenePlayCountLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesPlayCount(ctx),
},
SceneOCount: &SceneOCountLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesOCount(ctx),
},
ScenePlayHistory: &ScenePlayHistoryLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesPlayHistory(ctx),
},
SceneLastPlayed: &SceneLastPlayedLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesLastPlayed(ctx),
},
SceneOHistory: &SceneOHistoryLoader{
wait: wait,
maxBatch: maxBatch,
fetch: m.fetchScenesOHistory(ctx),
},
}
newCtx := context.WithValue(r.Context(), loadersCtxKey, ldrs)
@@ -131,13 +183,9 @@ func toErrorSlice(err error) []error {
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.FindMany(ctx, keys)
return err
@@ -148,7 +196,7 @@ func (m Middleware) fetchScenes(ctx context.Context) func(keys []int) ([]*models
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.FindMany(ctx, keys)
return err
@@ -160,7 +208,7 @@ func (m Middleware) fetchImages(ctx context.Context) func(keys []int) ([]*models
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.FindMany(ctx, keys)
return err
@@ -172,7 +220,7 @@ func (m Middleware) fetchGalleries(ctx context.Context) func(keys []int) ([]*mod
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Performer.FindMany(ctx, keys)
return err
@@ -182,9 +230,21 @@ func (m Middleware) fetchPerformers(ctx context.Context) func(keys []int) ([]*mo
}
}
func (m Middleware) fetchPerformerCustomFields(ctx context.Context) func(keys []int) ([]models.CustomFieldMap, []error) {
return func(keys []int) (ret []models.CustomFieldMap, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Performer.GetCustomFieldsBulk(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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Studio.FindMany(ctx, keys)
return err
@@ -195,7 +255,7 @@ func (m Middleware) fetchStudios(ctx context.Context) func(keys []int) ([]*model
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 {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Tag.FindMany(ctx, keys)
return err
@@ -204,11 +264,11 @@ func (m Middleware) fetchTags(ctx context.Context) func(keys []int) ([]*models.T
}
}
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 {
func (m Middleware) fetchGroups(ctx context.Context) func(keys []int) ([]*models.Group, []error) {
return func(keys []int) (ret []*models.Group, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Movie.FindMany(ctx, keys)
ret, err = m.Repository.Group.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
@@ -217,7 +277,7 @@ func (m Middleware) fetchMovies(ctx context.Context) func(keys []int) ([]*models
func (m Middleware) fetchFiles(ctx context.Context) func(keys []models.FileID) ([]models.File, []error) {
return func(keys []models.FileID) (ret []models.File, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.File.Find(ctx, keys...)
return err
@@ -226,9 +286,20 @@ func (m Middleware) fetchFiles(ctx context.Context) func(keys []models.FileID) (
}
}
func (m Middleware) fetchFolders(ctx context.Context) func(keys []models.FolderID) ([]*models.Folder, []error) {
return func(keys []models.FolderID) (ret []*models.Folder, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Folder.FindMany(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyFileIDs(ctx, keys)
return err
@@ -239,7 +310,7 @@ func (m Middleware) fetchScenesFileIDs(ctx context.Context) func(keys []int) ([]
func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Image.GetManyFileIDs(ctx, keys)
return err
@@ -250,7 +321,7 @@ func (m Middleware) fetchImagesFileIDs(ctx context.Context) func(keys []int) ([]
func (m Middleware) fetchGalleriesFileIDs(ctx context.Context) func(keys []int) ([][]models.FileID, []error) {
return func(keys []int) (ret [][]models.FileID, errs []error) {
err := m.withTxn(ctx, func(ctx context.Context) error {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Gallery.GetManyFileIDs(ctx, keys)
return err
@@ -258,3 +329,58 @@ func (m Middleware) fetchGalleriesFileIDs(ctx context.Context) func(keys []int)
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesOCount(ctx context.Context) func(keys []int) ([]int, []error) {
return func(keys []int) (ret []int, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyOCount(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesPlayCount(ctx context.Context) func(keys []int) ([]int, []error) {
return func(keys []int) (ret []int, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyViewCount(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesOHistory(ctx context.Context) func(keys []int) ([][]time.Time, []error) {
return func(keys []int) (ret [][]time.Time, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyODates(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesPlayHistory(ctx context.Context) func(keys []int) ([][]time.Time, []error) {
return func(keys []int) (ret [][]time.Time, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyViewDates(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}
func (m Middleware) fetchScenesLastPlayed(ctx context.Context) func(keys []int) ([]*time.Time, []error) {
return func(keys []int) (ret []*time.Time, errs []error) {
err := m.Repository.WithDB(ctx, func(ctx context.Context) error {
var err error
ret, err = m.Repository.Scene.GetManyLastViewed(ctx, keys)
return err
})
return ret, toErrorSlice(err)
}
}

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"
)
// FolderLoaderConfig captures the config to create a new FolderLoader
type FolderLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []models.FolderID) ([]*models.Folder, []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
}
// NewFolderLoader creates a new FolderLoader given a fetch, wait, and maxBatch
func NewFolderLoader(config FolderLoaderConfig) *FolderLoader {
return &FolderLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// FolderLoader batches and caches requests
type FolderLoader struct {
// this method provides the data for the loader
fetch func(keys []models.FolderID) ([]*models.Folder, []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[models.FolderID]*models.Folder
// 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 *folderLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type folderLoaderBatch struct {
keys []models.FolderID
data []*models.Folder
error []error
closing bool
done chan struct{}
}
// Load a Folder by key, batching and caching will be applied automatically
func (l *FolderLoader) Load(key models.FolderID) (*models.Folder, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Folder.
// 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 *FolderLoader) LoadThunk(key models.FolderID) func() (*models.Folder, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Folder, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &folderLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Folder, error) {
<-batch.done
var data *models.Folder
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 *FolderLoader) LoadAll(keys []models.FolderID) ([]*models.Folder, []error) {
results := make([]func() (*models.Folder, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
folders := make([]*models.Folder, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
folders[i], errors[i] = thunk()
}
return folders, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Folders.
// 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 *FolderLoader) LoadAllThunk(keys []models.FolderID) func() ([]*models.Folder, []error) {
results := make([]func() (*models.Folder, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Folder, []error) {
folders := make([]*models.Folder, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
folders[i], errors[i] = thunk()
}
return folders, 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 *FolderLoader) Prime(key models.FolderID, value *models.Folder) 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 *FolderLoader) Clear(key models.FolderID) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *FolderLoader) unsafeSet(key models.FolderID, value *models.Folder) {
if l.cache == nil {
l.cache = map[models.FolderID]*models.Folder{}
}
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 *folderLoaderBatch) keyIndex(l *FolderLoader, key models.FolderID) 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 *folderLoaderBatch) startTimer(l *FolderLoader) {
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 *folderLoaderBatch) end(l *FolderLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -9,10 +9,10 @@ import (
"github.com/stashapp/stash/pkg/models"
)
// MovieLoaderConfig captures the config to create a new MovieLoader
type MovieLoaderConfig struct {
// GroupLoaderConfig captures the config to create a new GroupLoader
type GroupLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*models.Movie, []error)
Fetch func(keys []int) ([]*models.Group, []error)
// Wait is how long wait before sending a batch
Wait time.Duration
@@ -21,19 +21,19 @@ type MovieLoaderConfig struct {
MaxBatch int
}
// NewMovieLoader creates a new MovieLoader given a fetch, wait, and maxBatch
func NewMovieLoader(config MovieLoaderConfig) *MovieLoader {
return &MovieLoader{
// NewGroupLoader creates a new GroupLoader given a fetch, wait, and maxBatch
func NewGroupLoader(config GroupLoaderConfig) *GroupLoader {
return &GroupLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// MovieLoader batches and caches requests
type MovieLoader struct {
// GroupLoader batches and caches requests
type GroupLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*models.Movie, []error)
fetch func(keys []int) ([]*models.Group, []error)
// how long to done before sending a batch
wait time.Duration
@@ -44,51 +44,51 @@ type MovieLoader struct {
// INTERNAL
// lazily created cache
cache map[int]*models.Movie
cache map[int]*models.Group
// 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
batch *groupLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type movieLoaderBatch struct {
type groupLoaderBatch struct {
keys []int
data []*models.Movie
data []*models.Group
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) {
// Load a Group by key, batching and caching will be applied automatically
func (l *GroupLoader) Load(key int) (*models.Group, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Movie.
// LoadThunk returns a function that when called will block waiting for a Group.
// 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) {
func (l *GroupLoader) LoadThunk(key int) func() (*models.Group, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*models.Movie, error) {
return func() (*models.Group, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &movieLoaderBatch{done: make(chan struct{})}
l.batch = &groupLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*models.Movie, error) {
return func() (*models.Group, error) {
<-batch.done
var data *models.Movie
var data *models.Group
if pos < len(batch.data) {
data = batch.data[pos]
}
@@ -113,43 +113,43 @@ func (l *MovieLoader) LoadThunk(key int) func() (*models.Movie, error) {
// 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))
func (l *GroupLoader) LoadAll(keys []int) ([]*models.Group, []error) {
results := make([]func() (*models.Group, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
movies := make([]*models.Movie, len(keys))
groups := make([]*models.Group, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
movies[i], errors[i] = thunk()
groups[i], errors[i] = thunk()
}
return movies, errors
return groups, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Movies.
// LoadAllThunk returns a function that when called will block waiting for a Groups.
// 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))
func (l *GroupLoader) LoadAllThunk(keys []int) func() ([]*models.Group, []error) {
results := make([]func() (*models.Group, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*models.Movie, []error) {
movies := make([]*models.Movie, len(keys))
return func() ([]*models.Group, []error) {
groups := make([]*models.Group, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
movies[i], errors[i] = thunk()
groups[i], errors[i] = thunk()
}
return movies, errors
return groups, 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 {
func (l *GroupLoader) Prime(key int, value *models.Group) bool {
l.mu.Lock()
var found bool
if _, found = l.cache[key]; !found {
@@ -163,22 +163,22 @@ func (l *MovieLoader) Prime(key int, value *models.Movie) bool {
}
// Clear the value at key from the cache, if it exists
func (l *MovieLoader) Clear(key int) {
func (l *GroupLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *MovieLoader) unsafeSet(key int, value *models.Movie) {
func (l *GroupLoader) unsafeSet(key int, value *models.Group) {
if l.cache == nil {
l.cache = map[int]*models.Movie{}
l.cache = map[int]*models.Group{}
}
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 {
func (b *groupLoaderBatch) keyIndex(l *GroupLoader, key int) int {
for i, existingKey := range b.keys {
if key == existingKey {
return i
@@ -202,7 +202,7 @@ func (b *movieLoaderBatch) keyIndex(l *MovieLoader, key int) int {
return pos
}
func (b *movieLoaderBatch) startTimer(l *MovieLoader) {
func (b *groupLoaderBatch) startTimer(l *GroupLoader) {
time.Sleep(l.wait)
l.mu.Lock()
@@ -218,7 +218,7 @@ func (b *movieLoaderBatch) startTimer(l *MovieLoader) {
b.end(l)
}
func (b *movieLoaderBatch) end(l *MovieLoader) {
func (b *groupLoaderBatch) end(l *GroupLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,222 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
)
// SceneLastPlayedLoaderConfig captures the config to create a new SceneLastPlayedLoader
type SceneLastPlayedLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]*time.Time, []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
}
// NewSceneLastPlayedLoader creates a new SceneLastPlayedLoader given a fetch, wait, and maxBatch
func NewSceneLastPlayedLoader(config SceneLastPlayedLoaderConfig) *SceneLastPlayedLoader {
return &SceneLastPlayedLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// SceneLastPlayedLoader batches and caches requests
type SceneLastPlayedLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]*time.Time, []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]*time.Time
// 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 *sceneLastPlayedLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type sceneLastPlayedLoaderBatch struct {
keys []int
data []*time.Time
error []error
closing bool
done chan struct{}
}
// Load a Time by key, batching and caching will be applied automatically
func (l *SceneLastPlayedLoader) Load(key int) (*time.Time, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Time.
// 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 *SceneLastPlayedLoader) LoadThunk(key int) func() (*time.Time, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (*time.Time, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &sceneLastPlayedLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (*time.Time, error) {
<-batch.done
var data *time.Time
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 *SceneLastPlayedLoader) LoadAll(keys []int) ([]*time.Time, []error) {
results := make([]func() (*time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
times := make([]*time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Times.
// 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 *SceneLastPlayedLoader) LoadAllThunk(keys []int) func() ([]*time.Time, []error) {
results := make([]func() (*time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]*time.Time, []error) {
times := make([]*time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, 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 *SceneLastPlayedLoader) Prime(key int, value *time.Time) 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 *SceneLastPlayedLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *SceneLastPlayedLoader) unsafeSet(key int, value *time.Time) {
if l.cache == nil {
l.cache = map[int]*time.Time{}
}
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 *sceneLastPlayedLoaderBatch) keyIndex(l *SceneLastPlayedLoader, 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 *sceneLastPlayedLoaderBatch) startTimer(l *SceneLastPlayedLoader) {
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 *sceneLastPlayedLoaderBatch) end(l *SceneLastPlayedLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,219 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
)
// SceneOCountLoaderConfig captures the config to create a new SceneOCountLoader
type SceneOCountLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]int, []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
}
// NewSceneOCountLoader creates a new SceneOCountLoader given a fetch, wait, and maxBatch
func NewSceneOCountLoader(config SceneOCountLoaderConfig) *SceneOCountLoader {
return &SceneOCountLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// SceneOCountLoader batches and caches requests
type SceneOCountLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]int, []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]int
// 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 *sceneOCountLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type sceneOCountLoaderBatch struct {
keys []int
data []int
error []error
closing bool
done chan struct{}
}
// Load a int by key, batching and caching will be applied automatically
func (l *SceneOCountLoader) Load(key int) (int, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a int.
// 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 *SceneOCountLoader) LoadThunk(key int) func() (int, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (int, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &sceneOCountLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (int, error) {
<-batch.done
var data int
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 *SceneOCountLoader) LoadAll(keys []int) ([]int, []error) {
results := make([]func() (int, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
ints := make([]int, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
ints[i], errors[i] = thunk()
}
return ints, errors
}
// LoadAllThunk returns a function that when called will block waiting for a ints.
// 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 *SceneOCountLoader) LoadAllThunk(keys []int) func() ([]int, []error) {
results := make([]func() (int, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]int, []error) {
ints := make([]int, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
ints[i], errors[i] = thunk()
}
return ints, 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 *SceneOCountLoader) Prime(key int, value int) 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 *SceneOCountLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *SceneOCountLoader) unsafeSet(key int, value int) {
if l.cache == nil {
l.cache = map[int]int{}
}
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 *sceneOCountLoaderBatch) keyIndex(l *SceneOCountLoader, 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 *sceneOCountLoaderBatch) startTimer(l *SceneOCountLoader) {
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 *sceneOCountLoaderBatch) end(l *SceneOCountLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,223 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
)
// SceneOHistoryLoaderConfig captures the config to create a new SceneOHistoryLoader
type SceneOHistoryLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]time.Time, []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
}
// NewSceneOHistoryLoader creates a new SceneOHistoryLoader given a fetch, wait, and maxBatch
func NewSceneOHistoryLoader(config SceneOHistoryLoaderConfig) *SceneOHistoryLoader {
return &SceneOHistoryLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// SceneOHistoryLoader batches and caches requests
type SceneOHistoryLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]time.Time, []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][]time.Time
// 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 *sceneOHistoryLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type sceneOHistoryLoaderBatch struct {
keys []int
data [][]time.Time
error []error
closing bool
done chan struct{}
}
// Load a Time by key, batching and caching will be applied automatically
func (l *SceneOHistoryLoader) Load(key int) ([]time.Time, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Time.
// 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 *SceneOHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]time.Time, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &sceneOHistoryLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]time.Time, error) {
<-batch.done
var data []time.Time
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 *SceneOHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) {
results := make([]func() ([]time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
times := make([][]time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Times.
// 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 *SceneOHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, []error) {
results := make([]func() ([]time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]time.Time, []error) {
times := make([][]time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, 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 *SceneOHistoryLoader) Prime(key int, value []time.Time) 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([]time.Time, 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 *SceneOHistoryLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *SceneOHistoryLoader) unsafeSet(key int, value []time.Time) {
if l.cache == nil {
l.cache = map[int][]time.Time{}
}
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 *sceneOHistoryLoaderBatch) keyIndex(l *SceneOHistoryLoader, 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 *sceneOHistoryLoaderBatch) startTimer(l *SceneOHistoryLoader) {
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 *sceneOHistoryLoaderBatch) end(l *SceneOHistoryLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,219 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
)
// ScenePlayCountLoaderConfig captures the config to create a new ScenePlayCountLoader
type ScenePlayCountLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([]int, []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
}
// NewScenePlayCountLoader creates a new ScenePlayCountLoader given a fetch, wait, and maxBatch
func NewScenePlayCountLoader(config ScenePlayCountLoaderConfig) *ScenePlayCountLoader {
return &ScenePlayCountLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// ScenePlayCountLoader batches and caches requests
type ScenePlayCountLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([]int, []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]int
// 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 *scenePlayCountLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type scenePlayCountLoaderBatch struct {
keys []int
data []int
error []error
closing bool
done chan struct{}
}
// Load a int by key, batching and caching will be applied automatically
func (l *ScenePlayCountLoader) Load(key int) (int, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a int.
// 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 *ScenePlayCountLoader) LoadThunk(key int) func() (int, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() (int, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &scenePlayCountLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() (int, error) {
<-batch.done
var data int
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 *ScenePlayCountLoader) LoadAll(keys []int) ([]int, []error) {
results := make([]func() (int, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
ints := make([]int, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
ints[i], errors[i] = thunk()
}
return ints, errors
}
// LoadAllThunk returns a function that when called will block waiting for a ints.
// 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 *ScenePlayCountLoader) LoadAllThunk(keys []int) func() ([]int, []error) {
results := make([]func() (int, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([]int, []error) {
ints := make([]int, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
ints[i], errors[i] = thunk()
}
return ints, 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 *ScenePlayCountLoader) Prime(key int, value int) 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 *ScenePlayCountLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *ScenePlayCountLoader) unsafeSet(key int, value int) {
if l.cache == nil {
l.cache = map[int]int{}
}
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 *scenePlayCountLoaderBatch) keyIndex(l *ScenePlayCountLoader, 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 *scenePlayCountLoaderBatch) startTimer(l *ScenePlayCountLoader) {
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 *scenePlayCountLoaderBatch) end(l *ScenePlayCountLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -0,0 +1,223 @@
// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
package loaders
import (
"sync"
"time"
)
// ScenePlayHistoryLoaderConfig captures the config to create a new ScenePlayHistoryLoader
type ScenePlayHistoryLoaderConfig struct {
// Fetch is a method that provides the data for the loader
Fetch func(keys []int) ([][]time.Time, []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
}
// NewScenePlayHistoryLoader creates a new ScenePlayHistoryLoader given a fetch, wait, and maxBatch
func NewScenePlayHistoryLoader(config ScenePlayHistoryLoaderConfig) *ScenePlayHistoryLoader {
return &ScenePlayHistoryLoader{
fetch: config.Fetch,
wait: config.Wait,
maxBatch: config.MaxBatch,
}
}
// ScenePlayHistoryLoader batches and caches requests
type ScenePlayHistoryLoader struct {
// this method provides the data for the loader
fetch func(keys []int) ([][]time.Time, []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][]time.Time
// 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 *scenePlayHistoryLoaderBatch
// mutex to prevent races
mu sync.Mutex
}
type scenePlayHistoryLoaderBatch struct {
keys []int
data [][]time.Time
error []error
closing bool
done chan struct{}
}
// Load a Time by key, batching and caching will be applied automatically
func (l *ScenePlayHistoryLoader) Load(key int) ([]time.Time, error) {
return l.LoadThunk(key)()
}
// LoadThunk returns a function that when called will block waiting for a Time.
// 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 *ScenePlayHistoryLoader) LoadThunk(key int) func() ([]time.Time, error) {
l.mu.Lock()
if it, ok := l.cache[key]; ok {
l.mu.Unlock()
return func() ([]time.Time, error) {
return it, nil
}
}
if l.batch == nil {
l.batch = &scenePlayHistoryLoaderBatch{done: make(chan struct{})}
}
batch := l.batch
pos := batch.keyIndex(l, key)
l.mu.Unlock()
return func() ([]time.Time, error) {
<-batch.done
var data []time.Time
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 *ScenePlayHistoryLoader) LoadAll(keys []int) ([][]time.Time, []error) {
results := make([]func() ([]time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
times := make([][]time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, errors
}
// LoadAllThunk returns a function that when called will block waiting for a Times.
// 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 *ScenePlayHistoryLoader) LoadAllThunk(keys []int) func() ([][]time.Time, []error) {
results := make([]func() ([]time.Time, error), len(keys))
for i, key := range keys {
results[i] = l.LoadThunk(key)
}
return func() ([][]time.Time, []error) {
times := make([][]time.Time, len(keys))
errors := make([]error, len(keys))
for i, thunk := range results {
times[i], errors[i] = thunk()
}
return times, 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 *ScenePlayHistoryLoader) Prime(key int, value []time.Time) 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([]time.Time, 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 *ScenePlayHistoryLoader) Clear(key int) {
l.mu.Lock()
delete(l.cache, key)
l.mu.Unlock()
}
func (l *ScenePlayHistoryLoader) unsafeSet(key int, value []time.Time) {
if l.cache == nil {
l.cache = map[int][]time.Time{}
}
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 *scenePlayHistoryLoaderBatch) keyIndex(l *ScenePlayHistoryLoader, 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 *scenePlayHistoryLoaderBatch) startTimer(l *ScenePlayHistoryLoader) {
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 *scenePlayHistoryLoaderBatch) end(l *ScenePlayHistoryLoader) {
b.data, b.error = l.fetch(b.keys)
close(b.done)
}

View File

@@ -1,64 +1,100 @@
package api
import (
"errors"
"fmt"
"io"
"strconv"
"time"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
"github.com/stashapp/stash/pkg/sliceutil"
)
type BaseFile interface{}
type BaseFile interface {
IsBaseFile()
}
type VisualFile interface {
IsVisualFile()
}
func convertVisualFile(f models.File) (VisualFile, error) {
switch f := f.(type) {
case VisualFile:
return f, nil
case *models.VideoFile:
return &VideoFile{VideoFile: f}, nil
case *models.ImageFile:
return &ImageFile{ImageFile: f}, nil
default:
return nil, fmt.Errorf("file %s is not a visual file", f.Base().Path)
}
}
func convertBaseFile(f models.File) BaseFile {
if f == nil {
return nil
}
switch f := f.(type) {
case BaseFile:
return f
case *models.VideoFile:
return &VideoFile{VideoFile: f}
case *models.ImageFile:
return &ImageFile{ImageFile: f}
case *models.BaseFile:
return &BasicFile{BaseFile: f}
default:
panic("unknown file type")
}
}
func convertBaseFiles(files []models.File) []BaseFile {
return sliceutil.Map(files, convertBaseFile)
}
type GalleryFile struct {
*models.BaseFile
}
var ErrTimestamp = errors.New("cannot parse Timestamp")
func (GalleryFile) IsBaseFile() {}
func MarshalTimestamp(t time.Time) graphql.Marshaler {
if t.IsZero() {
return graphql.Null
}
func (GalleryFile) IsVisualFile() {}
return graphql.WriterFunc(func(w io.Writer) {
_, err := io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano)))
if err != nil {
logger.Warnf("could not marshal timestamp: %v", err)
}
})
func (f *GalleryFile) Fingerprints() []models.Fingerprint {
return f.BaseFile.Fingerprints
}
func UnmarshalTimestamp(v interface{}) (time.Time, error) {
if tmpStr, ok := v.(string); ok {
if len(tmpStr) == 0 {
return time.Time{}, fmt.Errorf("%w: empty string", ErrTimestamp)
}
switch tmpStr[0] {
case '>', '<':
d, err := time.ParseDuration(tmpStr[1:])
if err != nil {
return time.Time{}, fmt.Errorf("%w: cannot parse %v-duration: %v", ErrTimestamp, tmpStr[0], err)
}
t := time.Now()
// Compute point in time:
if tmpStr[0] == '<' {
t = t.Add(-d)
} else {
t = t.Add(d)
}
return t, nil
}
return utils.ParseDateStringAsTime(tmpStr)
}
return time.Time{}, fmt.Errorf("%w: not a string", ErrTimestamp)
type VideoFile struct {
*models.VideoFile
}
func (VideoFile) IsBaseFile() {}
func (VideoFile) IsVisualFile() {}
func (f *VideoFile) Fingerprints() []models.Fingerprint {
return f.VideoFile.Fingerprints
}
type ImageFile struct {
*models.ImageFile
}
func (ImageFile) IsBaseFile() {}
func (ImageFile) IsVisualFile() {}
func (f *ImageFile) Fingerprints() []models.Fingerprint {
return f.ImageFile.Fingerprints
}
type BasicFile struct {
*models.BaseFile
}
func (BasicFile) IsBaseFile() {}
func (BasicFile) IsVisualFile() {}
func (f *BasicFile) Fingerprints() []models.Fingerprint {
return f.BaseFile.Fingerprints
}

View File

@@ -0,0 +1,37 @@
package api
import (
"encoding/json"
"fmt"
"io"
"github.com/99designs/gqlgen/graphql"
)
func MarshalPluginConfigMap(val map[string]map[string]interface{}) graphql.Marshaler {
return graphql.WriterFunc(func(w io.Writer) {
err := json.NewEncoder(w).Encode(val)
if err != nil {
panic(err)
}
})
}
func UnmarshalPluginConfigMap(v interface{}) (map[string]map[string]interface{}, error) {
m, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%T is not a plugin config map", v)
}
result := make(map[string]map[string]interface{})
for k, v := range m {
val, ok := v.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("key %s (%T) is not a map", k, v)
}
result[k] = val
}
return result, nil
}

View File

@@ -11,9 +11,8 @@ import (
"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/plugin/hook"
"github.com/stashapp/stash/pkg/scraper"
"github.com/stashapp/stash/pkg/txn"
)
var (
@@ -29,15 +28,15 @@ var (
)
type hookExecutor interface {
ExecutePostHooks(ctx context.Context, id int, hookType plugin.HookTriggerEnum, input interface{}, inputFields []string)
ExecutePostHooks(ctx context.Context, id int, hookType hook.TriggerEnum, input interface{}, inputFields []string)
}
type Resolver struct {
txnManager txn.Manager
repository manager.Repository
repository models.Repository
sceneService manager.SceneService
imageService manager.ImageService
galleryService manager.GalleryService
groupService manager.GroupService
hookExecutor hookExecutor
}
@@ -73,18 +72,44 @@ func (r *Resolver) SceneMarker() SceneMarkerResolver {
func (r *Resolver) Studio() StudioResolver {
return &studioResolver{r}
}
func (r *Resolver) Movie() MovieResolver {
return &movieResolver{r}
func (r *Resolver) Group() GroupResolver {
return &groupResolver{r}
}
func (r *Resolver) Movie() MovieResolver {
return &movieResolver{&groupResolver{r}}
}
func (r *Resolver) Subscription() SubscriptionResolver {
return &subscriptionResolver{r}
}
func (r *Resolver) Tag() TagResolver {
return &tagResolver{r}
}
func (r *Resolver) GalleryFile() GalleryFileResolver {
return &galleryFileResolver{r}
}
func (r *Resolver) VideoFile() VideoFileResolver {
return &videoFileResolver{r}
}
func (r *Resolver) ImageFile() ImageFileResolver {
return &imageFileResolver{r}
}
func (r *Resolver) BasicFile() BasicFileResolver {
return &basicFileResolver{r}
}
func (r *Resolver) Folder() FolderResolver {
return &folderResolver{r}
}
func (r *Resolver) SavedFilter() SavedFilterResolver {
return &savedFilterResolver{r}
}
func (r *Resolver) Plugin() PluginResolver {
return &pluginResolver{r}
}
func (r *Resolver) ConfigResult() ConfigResultResolver {
return &configResultResolver{r}
}
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
@@ -97,16 +122,27 @@ type sceneResolver struct{ *Resolver }
type sceneMarkerResolver struct{ *Resolver }
type imageResolver struct{ *Resolver }
type studioResolver struct{ *Resolver }
type movieResolver struct{ *Resolver }
// movie is group under the hood
type groupResolver struct{ *Resolver }
type movieResolver struct{ *groupResolver }
type tagResolver struct{ *Resolver }
type galleryFileResolver struct{ *Resolver }
type videoFileResolver struct{ *Resolver }
type imageFileResolver struct{ *Resolver }
type basicFileResolver struct{ *Resolver }
type folderResolver struct{ *Resolver }
type savedFilterResolver struct{ *Resolver }
type pluginResolver struct{ *Resolver }
type configResultResolver struct{ *Resolver }
func (r *Resolver) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithTxn(ctx, r.txnManager, fn)
return r.repository.WithTxn(ctx, fn)
}
func (r *Resolver) withReadTxn(ctx context.Context, fn func(ctx context.Context) error) error {
return txn.WithReadTxn(ctx, r.txnManager, fn)
return r.repository.WithReadTxn(ctx, fn)
}
func (r *queryResolver) MarkerWall(ctx context.Context, q *string) (ret []*models.SceneMarker, err error) {
@@ -145,27 +181,90 @@ 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
sceneQB := 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)
totalOCount, _ := scenesQB.OCount(ctx)
totalPlayDuration, _ := scenesQB.PlayDuration(ctx)
totalPlayCount, _ := scenesQB.PlayCount(ctx)
uniqueScenePlayCount, _ := scenesQB.UniqueScenePlayCount(ctx)
studioQB := repo.Studio
performerQB := repo.Performer
movieQB := repo.Group
tagQB := repo.Tag
// embrace the error
scenesCount, err := sceneQB.Count(ctx)
if err != nil {
return err
}
scenesSize, err := sceneQB.Size(ctx)
if err != nil {
return err
}
scenesDuration, err := sceneQB.Duration(ctx)
if err != nil {
return err
}
imageCount, err := imageQB.Count(ctx)
if err != nil {
return err
}
imageSize, err := imageQB.Size(ctx)
if err != nil {
return err
}
galleryCount, err := galleryQB.Count(ctx)
if err != nil {
return err
}
performersCount, err := performerQB.Count(ctx)
if err != nil {
return err
}
studiosCount, err := studioQB.Count(ctx)
if err != nil {
return err
}
groupsCount, err := movieQB.Count(ctx)
if err != nil {
return err
}
tagsCount, err := tagQB.Count(ctx)
if err != nil {
return err
}
scenesTotalOCount, err := sceneQB.GetAllOCount(ctx)
if err != nil {
return err
}
imagesTotalOCount, err := imageQB.OCount(ctx)
if err != nil {
return err
}
totalOCount := scenesTotalOCount + imagesTotalOCount
totalPlayDuration, err := sceneQB.PlayDuration(ctx)
if err != nil {
return err
}
totalPlayCount, err := sceneQB.CountAllViews(ctx)
if err != nil {
return err
}
uniqueScenePlayCount, err := sceneQB.CountUniqueViews(ctx)
if err != nil {
return err
}
ret = StatsResultType{
SceneCount: scenesCount,
@@ -176,7 +275,8 @@ func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
GalleryCount: galleryCount,
PerformerCount: performersCount,
StudioCount: studiosCount,
MovieCount: moviesCount,
GroupCount: groupsCount,
MovieCount: groupsCount,
TagCount: tagsCount,
TotalOCount: totalOCount,
TotalPlayDuration: totalPlayDuration,

View File

@@ -0,0 +1,25 @@
package api
import (
"context"
"github.com/stashapp/stash/internal/manager/config"
)
func (r *configResultResolver) Plugins(ctx context.Context, obj *ConfigResult, include []string) (map[string]map[string]interface{}, error) {
if len(include) == 0 {
ret := config.GetInstance().GetAllPluginConfiguration()
return ret, nil
}
ret := make(map[string]map[string]interface{})
for _, plugin := range include {
c := config.GetInstance().GetPluginConfiguration(plugin)
if len(c) > 0 {
ret[plugin] = c
}
}
return ret, nil
}

View File

@@ -0,0 +1,80 @@
package api
import (
"context"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/pkg/models"
)
func fingerprintResolver(fp models.Fingerprints, type_ string) (*string, error) {
fingerprint := fp.For(type_)
if fingerprint != nil {
value := fingerprint.Value()
return &value, nil
}
return nil, nil
}
func (r *galleryFileResolver) Fingerprint(ctx context.Context, obj *GalleryFile, type_ string) (*string, error) {
return fingerprintResolver(obj.BaseFile.Fingerprints, type_)
}
func (r *imageFileResolver) Fingerprint(ctx context.Context, obj *ImageFile, type_ string) (*string, error) {
return fingerprintResolver(obj.ImageFile.Fingerprints, type_)
}
func (r *videoFileResolver) Fingerprint(ctx context.Context, obj *VideoFile, type_ string) (*string, error) {
return fingerprintResolver(obj.VideoFile.Fingerprints, type_)
}
func (r *basicFileResolver) Fingerprint(ctx context.Context, obj *BasicFile, type_ string) (*string, error) {
return fingerprintResolver(obj.BaseFile.Fingerprints, type_)
}
func (r *galleryFileResolver) ParentFolder(ctx context.Context, obj *GalleryFile) (*models.Folder, error) {
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
}
func (r *imageFileResolver) ParentFolder(ctx context.Context, obj *ImageFile) (*models.Folder, error) {
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
}
func (r *videoFileResolver) ParentFolder(ctx context.Context, obj *VideoFile) (*models.Folder, error) {
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
}
func (r *basicFileResolver) ParentFolder(ctx context.Context, obj *BasicFile) (*models.Folder, error) {
return loaders.From(ctx).FolderByID.Load(obj.ParentFolderID)
}
func zipFileResolver(ctx context.Context, zipFileID *models.FileID) (*BasicFile, error) {
if zipFileID == nil {
return nil, nil
}
f, err := loaders.From(ctx).FileByID.Load(*zipFileID)
if err != nil {
return nil, err
}
return &BasicFile{
BaseFile: f.Base(),
}, nil
}
func (r *galleryFileResolver) ZipFile(ctx context.Context, obj *GalleryFile) (*BasicFile, error) {
return zipFileResolver(ctx, obj.ZipFileID)
}
func (r *imageFileResolver) ZipFile(ctx context.Context, obj *ImageFile) (*BasicFile, error) {
return zipFileResolver(ctx, obj.ZipFileID)
}
func (r *videoFileResolver) ZipFile(ctx context.Context, obj *VideoFile) (*BasicFile, error) {
return zipFileResolver(ctx, obj.ZipFileID)
}
func (r *basicFileResolver) ZipFile(ctx context.Context, obj *BasicFile) (*BasicFile, error) {
return zipFileResolver(ctx, obj.ZipFileID)
}

View File

@@ -0,0 +1,20 @@
package api
import (
"context"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/pkg/models"
)
func (r *folderResolver) ParentFolder(ctx context.Context, obj *models.Folder) (*models.Folder, error) {
if obj.ParentFolderID == nil {
return nil, nil
}
return loaders.From(ctx).FolderByID.Load(*obj.ParentFolderID)
}
func (r *folderResolver) ZipFile(ctx context.Context, obj *models.Folder) (*BasicFile, error) {
return zipFileResolver(ctx, obj.ZipFileID)
}

View File

@@ -2,28 +2,16 @@ package api
import (
"context"
"time"
"fmt"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
)
func (r *galleryResolver) getPrimaryFile(ctx context.Context, obj *models.Gallery) (models.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) ([]models.File, error) {
fileIDs, err := loaders.From(ctx).GalleryFiles.Load(obj.ID)
if err != nil {
@@ -78,38 +66,6 @@ func (r *galleryResolver) Folder(ctx context.Context, obj *models.Gallery) (*mod
return ret, 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 image first
@@ -130,26 +86,6 @@ func (r *galleryResolver) Date(ctx context.Context, obj *models.Gallery) (*strin
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
}
@@ -255,3 +191,28 @@ func (r *galleryResolver) Urls(ctx context.Context, obj *models.Gallery) ([]stri
return obj.URLs.List(), nil
}
func (r *galleryResolver) Paths(ctx context.Context, obj *models.Gallery) (*GalleryPathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewGalleryURLBuilder(baseURL, obj)
return &GalleryPathsType{
Cover: builder.GetCoverURL(),
Preview: builder.GetPreviewURL(),
}, nil
}
func (r *galleryResolver) Image(ctx context.Context, obj *models.Gallery, index int) (ret *models.Image, err error) {
if index < 0 {
return nil, fmt.Errorf("index must >= 0")
}
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Image.FindByGalleryIDIndex(ctx, obj.ID, uint(index))
return err
}); err != nil {
return nil, err
}
return
}

View File

@@ -2,35 +2,12 @@ package api
import (
"context"
"fmt"
"time"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/models"
)
func convertVisualFile(f models.File) (models.VisualFile, error) {
vf, ok := f.(models.VisualFile)
if !ok {
return nil, fmt.Errorf("file %s is not a visual file", f.Base().Path)
}
return vf, nil
}
func (r *imageResolver) getPrimaryFile(ctx context.Context, obj *models.Image) (models.VisualFile, error) {
if obj.PrimaryFileID != nil {
f, err := loaders.From(ctx).FileByID.Load(*obj.PrimaryFileID)
if err != nil {
return nil, err
}
return convertVisualFile(f)
}
return nil, nil
}
func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]models.File, error) {
fileIDs, err := loaders.From(ctx).ImageFiles.Load(obj.ID)
if err != nil {
@@ -41,38 +18,13 @@ func (r *imageResolver) getFiles(ctx context.Context, obj *models.Image) ([]mode
return files, 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.GetWidth()
height := f.GetHeight()
size := f.Base().Size
return &ImageFileType{
Size: int(size),
Width: width,
Height: height,
}, nil
}
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]models.VisualFile, error) {
func (r *imageResolver) VisualFiles(ctx context.Context, obj *models.Image) ([]VisualFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
ret := make([]models.VisualFile, len(files))
ret := make([]VisualFile, len(files))
for i, f := range files {
ret[i], err = convertVisualFile(f)
if err != nil {
@@ -91,13 +43,13 @@ func (r *imageResolver) Date(ctx context.Context, obj *models.Image) (*string, e
return nil, nil
}
func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*models.ImageFile, error) {
func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*ImageFile, error) {
files, err := r.getFiles(ctx, obj)
if err != nil {
return nil, err
}
var ret []*models.ImageFile
var ret []*ImageFile
for _, f := range files {
// filter out non-image files
@@ -106,24 +58,14 @@ func (r *imageResolver) Files(ctx context.Context, obj *models.Image) ([]*models
continue
}
ret = append(ret, imageFile)
ret = append(ret, &ImageFile{
ImageFile: imageFile,
})
}
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.Base().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)
@@ -151,14 +93,6 @@ func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) (ret [
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
}

View File

@@ -5,16 +5,13 @@ import (
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
"github.com/stashapp/stash/pkg/hash/md5"
"github.com/stashapp/stash/pkg/group"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/performer"
"github.com/stashapp/stash/pkg/scene"
)
func (r *movieResolver) Checksum(ctx context.Context, obj *models.Movie) (string, error) {
// generate checksum from movie name
return md5.FromString(obj.Name), nil
}
func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, error) {
func (r *groupResolver) Date(ctx context.Context, obj *models.Group) (*string, error) {
if obj.Date != nil {
result := obj.Date.String()
return &result, nil
@@ -22,19 +19,40 @@ func (r *movieResolver) Date(ctx context.Context, obj *models.Movie) (*string, e
return nil, nil
}
func (r *movieResolver) Rating(ctx context.Context, obj *models.Movie) (*int, error) {
if obj.Rating != nil {
rating := models.Rating100To5(*obj.Rating)
return &rating, nil
}
return nil, nil
}
func (r *movieResolver) Rating100(ctx context.Context, obj *models.Movie) (*int, error) {
func (r *groupResolver) Rating100(ctx context.Context, obj *models.Group) (*int, error) {
return obj.Rating, nil
}
func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *models.Studio, err error) {
func (r *groupResolver) URL(ctx context.Context, obj *models.Group) (*string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Group)
}); err != nil {
return nil, err
}
}
urls := obj.URLs.List()
if len(urls) == 0 {
return nil, nil
}
return &urls[0], nil
}
func (r *groupResolver) Urls(ctx context.Context, obj *models.Group) ([]string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Group)
}); err != nil {
return nil, err
}
}
return obj.URLs.List(), nil
}
func (r *groupResolver) Studio(ctx context.Context, obj *models.Group) (ret *models.Studio, err error) {
if obj.StudioID == nil {
return nil, nil
}
@@ -42,26 +60,102 @@ func (r *movieResolver) Studio(ctx context.Context, obj *models.Movie) (ret *mod
return loaders.From(ctx).StudioByID.Load(*obj.StudioID)
}
func (r *movieResolver) FrontImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
func (r groupResolver) Tags(ctx context.Context, obj *models.Group) (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.Group)
}); err != nil {
return nil, err
}
}
var errs []error
ret, errs = loaders.From(ctx).TagByID.LoadAll(obj.TagIDs.List())
return ret, firstError(errs)
}
func (r groupResolver) relatedGroups(ctx context.Context, rgd models.RelatedGroupDescriptions) (ret []*GroupDescription, err error) {
// rgd must be loaded
gds := rgd.List()
ids := make([]int, len(gds))
for i, gd := range gds {
ids[i] = gd.GroupID
}
groups, errs := loaders.From(ctx).GroupByID.LoadAll(ids)
err = firstError(errs)
if err != nil {
return
}
ret = make([]*GroupDescription, len(groups))
for i, group := range groups {
ret[i] = &GroupDescription{Group: group}
d := gds[i].Description
if d != "" {
ret[i].Description = &d
}
}
return ret, firstError(errs)
}
func (r groupResolver) ContainingGroups(ctx context.Context, obj *models.Group) (ret []*GroupDescription, err error) {
if !obj.ContainingGroups.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadContainingGroupIDs(ctx, r.repository.Group)
}); err != nil {
return nil, err
}
}
return r.relatedGroups(ctx, obj.ContainingGroups)
}
func (r groupResolver) SubGroups(ctx context.Context, obj *models.Group) (ret []*GroupDescription, err error) {
if !obj.SubGroups.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadSubGroupIDs(ctx, r.repository.Group)
}); err != nil {
return nil, err
}
}
return r.relatedGroups(ctx, obj.SubGroups)
}
func (r *groupResolver) SubGroupCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = group.CountByContainingGroupID(ctx, r.repository.Group, obj.ID, depth)
return err
}); err != nil {
return 0, err
}
return ret, nil
}
func (r *groupResolver) FrontImagePath(ctx context.Context, obj *models.Group) (*string, error) {
var hasImage bool
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
hasImage, err = r.repository.Movie.HasFrontImage(ctx, obj.ID)
hasImage, err = r.repository.Group.HasFrontImage(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieFrontImageURL(hasImage)
imagePath := urlbuilders.NewGroupURLBuilder(baseURL, obj).GetGroupFrontImageURL(hasImage)
return &imagePath, nil
}
func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*string, error) {
func (r *groupResolver) BackImagePath(ctx context.Context, obj *models.Group) (*string, error) {
var hasImage bool
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
hasImage, err = r.repository.Movie.HasBackImage(ctx, obj.ID)
hasImage, err = r.repository.Group.HasBackImage(ctx, obj.ID)
return err
}); err != nil {
return nil, err
@@ -73,13 +167,13 @@ func (r *movieResolver) BackImagePath(ctx context.Context, obj *models.Movie) (*
}
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
imagePath := urlbuilders.NewMovieURLBuilder(baseURL, obj).GetMovieBackImageURL()
imagePath := urlbuilders.NewGroupURLBuilder(baseURL, obj).GetGroupBackImageURL()
return &imagePath, nil
}
func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret int, err error) {
func (r *groupResolver) SceneCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Scene.CountByMovieID(ctx, obj.ID)
ret, err = scene.CountByGroupID(ctx, r.repository.Scene, obj.ID, depth)
return err
}); err != nil {
return 0, err
@@ -88,10 +182,21 @@ func (r *movieResolver) SceneCount(ctx context.Context, obj *models.Movie) (ret
return ret, nil
}
func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*models.Scene, err error) {
func (r *groupResolver) PerformerCount(ctx context.Context, obj *models.Group, depth *int) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = performer.CountByGroupID(ctx, r.repository.Performer, obj.ID, depth)
return err
}); err != nil {
return 0, err
}
return ret, nil
}
func (r *groupResolver) Scenes(ctx context.Context, obj *models.Group) (ret []*models.Scene, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
var err error
ret, err = r.repository.Scene.FindByMovieID(ctx, obj.ID)
ret, err = r.repository.Scene.FindByGroupID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
@@ -99,3 +204,14 @@ func (r *movieResolver) Scenes(ctx context.Context, obj *models.Movie) (ret []*m
return ret, nil
}
func (r *groupResolver) OCounter(ctx context.Context, obj *models.Group) (ret *int, err error) {
var count int
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
count, err = r.repository.Scene.OCountByGroupID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
}
return &count, nil
}

View File

@@ -3,7 +3,6 @@ package api
import (
"context"
"strconv"
"strings"
"github.com/stashapp/stash/internal/api/loaders"
"github.com/stashapp/stash/internal/api/urlbuilders"
@@ -13,24 +12,6 @@ import (
"github.com/stashapp/stash/pkg/performer"
)
// Checksum is deprecated
func (r *performerResolver) Checksum(ctx context.Context, obj *models.Performer) (*string, error) {
return nil, nil
}
func (r *performerResolver) Aliases(ctx context.Context, obj *models.Performer) (*string, error) {
if !obj.Aliases.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadAliases(ctx, r.repository.Performer)
}); err != nil {
return nil, err
}
}
ret := strings.Join(obj.Aliases.List(), ", ")
return &ret, nil
}
func (r *performerResolver) AliasList(ctx context.Context, obj *models.Performer) ([]string, error) {
if !obj.Aliases.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
@@ -43,6 +24,79 @@ func (r *performerResolver) AliasList(ctx context.Context, obj *models.Performer
return obj.Aliases.List(), nil
}
func (r *performerResolver) URL(ctx context.Context, obj *models.Performer) (*string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Performer)
}); err != nil {
return nil, err
}
}
urls := obj.URLs.List()
if len(urls) == 0 {
return nil, nil
}
return &urls[0], nil
}
func (r *performerResolver) Twitter(ctx context.Context, obj *models.Performer) (*string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Performer)
}); err != nil {
return nil, err
}
}
urls := obj.URLs.List()
// find the first twitter url
for _, url := range urls {
if performer.IsTwitterURL(url) {
u := url
return &u, nil
}
}
return nil, nil
}
func (r *performerResolver) Instagram(ctx context.Context, obj *models.Performer) (*string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Performer)
}); err != nil {
return nil, err
}
}
urls := obj.URLs.List()
// find the first instagram url
for _, url := range urls {
if performer.IsInstagramURL(url) {
u := url
return &u, nil
}
}
return nil, nil
}
func (r *performerResolver) Urls(ctx context.Context, obj *models.Performer) ([]string, error) {
if !obj.URLs.Loaded() {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
return obj.LoadURLs(ctx, r.repository.Performer)
}); err != nil {
return nil, err
}
}
return obj.URLs.List(), nil
}
func (r *performerResolver) Height(ctx context.Context, obj *models.Performer) (*string, error) {
if obj.Height != nil {
ret := strconv.Itoa(*obj.Height)
@@ -125,9 +179,9 @@ func (r *performerResolver) GalleryCount(ctx context.Context, obj *models.Perfor
return ret, nil
}
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
func (r *performerResolver) GroupCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Movie.CountByPerformerID(ctx, obj.ID)
ret, err = r.repository.Group.CountByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return 0, err
@@ -136,6 +190,11 @@ func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performe
return ret, nil
}
// deprecated
func (r *performerResolver) MovieCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
return r.GroupCount(ctx, obj)
}
func (r *performerResolver) PerformerCount(ctx context.Context, obj *models.Performer) (ret int, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = performer.CountByAppearsWith(ctx, r.repository.Performer, obj.ID)
@@ -186,14 +245,6 @@ func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer)
return stashIDsSliceToPtrSlice(obj.StashIDs.List()), 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
}
@@ -206,9 +257,9 @@ func (r *performerResolver) DeathDate(ctx context.Context, obj *models.Performer
return nil, nil
}
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Movie, err error) {
func (r *performerResolver) Groups(ctx context.Context, obj *models.Performer) (ret []*models.Group, err error) {
if err := r.withReadTxn(ctx, func(ctx context.Context) error {
ret, err = r.repository.Movie.FindByPerformerID(ctx, obj.ID)
ret, err = r.repository.Group.FindByPerformerID(ctx, obj.ID)
return err
}); err != nil {
return nil, err
@@ -216,3 +267,21 @@ func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (
return ret, nil
}
func (r *performerResolver) CustomFields(ctx context.Context, obj *models.Performer) (map[string]interface{}, error) {
m, err := loaders.From(ctx).PerformerCustomFields.Load(obj.ID)
if err != nil {
return nil, err
}
if m == nil {
return make(map[string]interface{}), nil
}
return m, nil
}
// deprecated
func (r *performerResolver) Movies(ctx context.Context, obj *models.Performer) (ret []*models.Group, err error) {
return r.Groups(ctx, obj)
}

View File

@@ -0,0 +1,61 @@
package api
import (
"context"
"github.com/stashapp/stash/pkg/plugin"
)
type pluginURLBuilder struct {
BaseURL string
Plugin *plugin.Plugin
}
func (b pluginURLBuilder) javascript() []string {
ui := b.Plugin.UI
if len(ui.Javascript) == 0 && len(ui.ExternalScript) == 0 {
return nil
}
var ret []string
ret = append(ret, ui.ExternalScript...)
ret = append(ret, b.BaseURL+"/plugin/"+b.Plugin.ID+"/javascript")
return ret
}
func (b pluginURLBuilder) css() []string {
ui := b.Plugin.UI
if len(ui.CSS) == 0 && len(ui.ExternalCSS) == 0 {
return nil
}
var ret []string
ret = append(ret, b.Plugin.UI.ExternalCSS...)
ret = append(ret, b.BaseURL+"/plugin/"+b.Plugin.ID+"/css")
return ret
}
func (b *pluginURLBuilder) paths() *PluginPaths {
return &PluginPaths{
Javascript: b.javascript(),
CSS: b.css(),
}
}
func (r *pluginResolver) Paths(ctx context.Context, obj *plugin.Plugin) (*PluginPaths, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
b := pluginURLBuilder{
BaseURL: baseURL,
Plugin: obj,
}
return b.paths(), nil
}
func (r *pluginResolver) Requires(ctx context.Context, obj *plugin.Plugin) ([]string, error) {
return obj.UI.Requires, nil
}

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