mirror of
https://github.com/stashapp/stash.git
synced 2026-06-11 07:41:08 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a373a25ca | ||
|
|
e116775d60 | ||
|
|
c5bafeb15c | ||
|
|
205b24499b | ||
|
|
48035061ec | ||
|
|
e4b89064b1 | ||
|
|
efede32dd7 |
@@ -46,17 +46,17 @@ func createMissingStudio(ctx context.Context, endpoint string, w models.StudioRe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
studioPartial := s.Parent.ToPartial(s.Parent.StoredID, endpoint, nil, existingStashIDs)
|
||||
studioPartial := s.Parent.ToPartial(*s.Parent.StoredID, endpoint, nil, existingStashIDs)
|
||||
parentImage, err := s.Parent.GetImage(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := studio.ValidateModify(ctx, *studioPartial, w); err != nil {
|
||||
if err := studio.ValidateModify(ctx, studioPartial, w); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = w.UpdatePartial(ctx, *studioPartial)
|
||||
_, err = w.UpdatePartial(ctx, studioPartial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -311,13 +311,13 @@ func (t *StashBoxBatchTagTask) processMatchedStudio(ctx context.Context, s *mode
|
||||
return err
|
||||
}
|
||||
|
||||
partial := s.ToPartial(s.StoredID, t.box.Endpoint, excluded, existingStashIDs)
|
||||
partial := s.ToPartial(*s.StoredID, t.box.Endpoint, excluded, existingStashIDs)
|
||||
|
||||
if err := studio.ValidateModify(ctx, *partial, qb); err != nil {
|
||||
if err := studio.ValidateModify(ctx, partial, qb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := qb.UpdatePartial(ctx, *partial); err != nil {
|
||||
if _, err := qb.UpdatePartial(ctx, partial); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -435,13 +435,13 @@ func (t *StashBoxBatchTagTask) processParentStudio(ctx context.Context, parent *
|
||||
return err
|
||||
}
|
||||
|
||||
partial := parent.ToPartial(parent.StoredID, t.box.Endpoint, excluded, existingStashIDs)
|
||||
partial := parent.ToPartial(*parent.StoredID, t.box.Endpoint, excluded, existingStashIDs)
|
||||
|
||||
if err := studio.ValidateModify(ctx, *partial, qb); err != nil {
|
||||
if err := studio.ValidateModify(ctx, partial, qb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := qb.UpdatePartial(ctx, *partial); err != nil {
|
||||
if _, err := qb.UpdatePartial(ctx, partial); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ func (s *ScrapedStudio) GetImage(ctx context.Context, excluded map[string]bool)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *ScrapedStudio) ToPartial(id *string, endpoint string, excluded map[string]bool, existingStashIDs []StashID) *StudioPartial {
|
||||
func (s *ScrapedStudio) ToPartial(id string, endpoint string, excluded map[string]bool, existingStashIDs []StashID) StudioPartial {
|
||||
ret := NewStudioPartial()
|
||||
ret.ID, _ = strconv.Atoi(*id)
|
||||
ret.ID, _ = strconv.Atoi(id)
|
||||
|
||||
if s.Name != "" && !excluded["name"] {
|
||||
ret.Name = NewOptionalString(s.Name)
|
||||
@@ -82,8 +82,6 @@ func (s *ScrapedStudio) ToPartial(id *string, endpoint string, excluded map[stri
|
||||
ret.ParentID = NewOptionalInt(parentID)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret.ParentID = NewOptionalIntPtr(nil)
|
||||
}
|
||||
|
||||
if s.RemoteSiteID != nil && endpoint != "" {
|
||||
@@ -97,7 +95,7 @@ func (s *ScrapedStudio) ToPartial(id *string, endpoint string, excluded map[stri
|
||||
})
|
||||
}
|
||||
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// A performer from a scraping operation...
|
||||
|
||||
@@ -249,3 +249,123 @@ func Test_scrapedToPerformerInput(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScrapedStudio_ToPartial(t *testing.T) {
|
||||
var (
|
||||
id = 1000
|
||||
idStr = strconv.Itoa(id)
|
||||
storedID = "storedID"
|
||||
parentStoredID = 2000
|
||||
parentStoredIDStr = strconv.Itoa(parentStoredID)
|
||||
name = "name"
|
||||
url = "url"
|
||||
remoteSiteID = "remoteSiteID"
|
||||
endpoint = "endpoint"
|
||||
image = "image"
|
||||
images = []string{image}
|
||||
|
||||
existingEndpoint = "existingEndpoint"
|
||||
existingStashID = StashID{"existingStashID", existingEndpoint}
|
||||
existingStashIDs = []StashID{existingStashID}
|
||||
)
|
||||
|
||||
fullStudio := ScrapedStudio{
|
||||
StoredID: &storedID,
|
||||
Name: name,
|
||||
URL: &url,
|
||||
Parent: &ScrapedStudio{
|
||||
StoredID: &parentStoredIDStr,
|
||||
},
|
||||
Image: &image,
|
||||
Images: images,
|
||||
RemoteSiteID: &remoteSiteID,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
id string
|
||||
endpoint string
|
||||
excluded map[string]bool
|
||||
existingStashIDs []StashID
|
||||
}
|
||||
|
||||
stdArgs := args{
|
||||
id: idStr,
|
||||
endpoint: endpoint,
|
||||
excluded: map[string]bool{},
|
||||
existingStashIDs: existingStashIDs,
|
||||
}
|
||||
|
||||
excludeAll := map[string]bool{
|
||||
"name": true,
|
||||
"url": true,
|
||||
"parent": true,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
o ScrapedStudio
|
||||
args args
|
||||
want StudioPartial
|
||||
}{
|
||||
{
|
||||
"full no exclusions",
|
||||
fullStudio,
|
||||
stdArgs,
|
||||
StudioPartial{
|
||||
ID: id,
|
||||
Name: NewOptionalString(name),
|
||||
URL: NewOptionalString(url),
|
||||
ParentID: NewOptionalInt(parentStoredID),
|
||||
StashIDs: &UpdateStashIDs{
|
||||
StashIDs: append(existingStashIDs, StashID{
|
||||
Endpoint: endpoint,
|
||||
StashID: remoteSiteID,
|
||||
}),
|
||||
Mode: RelationshipUpdateModeSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"exclude all",
|
||||
fullStudio,
|
||||
args{
|
||||
id: idStr,
|
||||
excluded: excludeAll,
|
||||
},
|
||||
StudioPartial{
|
||||
ID: id,
|
||||
},
|
||||
},
|
||||
{
|
||||
"overwrite stash id",
|
||||
fullStudio,
|
||||
args{
|
||||
id: idStr,
|
||||
excluded: excludeAll,
|
||||
endpoint: existingEndpoint,
|
||||
existingStashIDs: existingStashIDs,
|
||||
},
|
||||
StudioPartial{
|
||||
ID: id,
|
||||
StashIDs: &UpdateStashIDs{
|
||||
StashIDs: []StashID{{
|
||||
Endpoint: existingEndpoint,
|
||||
StashID: remoteSiteID,
|
||||
}},
|
||||
Mode: RelationshipUpdateModeSet,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := tt.o
|
||||
got := s.ToPartial(tt.args.id, tt.args.endpoint, tt.args.excluded, tt.args.existingStashIDs)
|
||||
|
||||
// unset updatedAt - we don't need to compare it
|
||||
got.UpdatedAt = OptionalTime{}
|
||||
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,11 +102,15 @@ func validateName(ctx context.Context, name string, disambig string, existingID
|
||||
},
|
||||
}
|
||||
|
||||
modifier := models.CriterionModifierIsNull
|
||||
|
||||
if disambig != "" {
|
||||
performerFilter.Disambiguation = &models.StringCriterionInput{
|
||||
Value: disambig,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
modifier = models.CriterionModifierEquals
|
||||
}
|
||||
|
||||
performerFilter.Disambiguation = &models.StringCriterionInput{
|
||||
Value: disambig,
|
||||
Modifier: modifier,
|
||||
}
|
||||
|
||||
if existingID == nil {
|
||||
|
||||
@@ -15,6 +15,9 @@ func nameFilter(n string) *models.PerformerFilterType {
|
||||
Value: n,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
Disambiguation: &models.StringCriterionInput{
|
||||
Modifier: models.CriterionModifierIsNull,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +44,6 @@ func TestValidateName(t *testing.T) {
|
||||
newName = "new name"
|
||||
newDisambig = "new disambiguation"
|
||||
)
|
||||
// existing1 := models.Performer{
|
||||
// Name: name1,
|
||||
// }
|
||||
// existing2 := models.Performer{
|
||||
// Name: name2,
|
||||
// Disambiguation: disambig,
|
||||
// }
|
||||
|
||||
pp := 1
|
||||
findFilter := &models.FindFilterType{
|
||||
|
||||
@@ -42,9 +42,9 @@ interface IDimension {
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const useContainerDimensions = <
|
||||
T extends HTMLElement = HTMLDivElement
|
||||
>(): [MutableRefObject<T | null>, IDimension] => {
|
||||
export const useContainerDimensions = <T extends HTMLElement = HTMLDivElement>(
|
||||
sensitivityThreshold = 20
|
||||
): [MutableRefObject<T | null>, IDimension] => {
|
||||
const target = useRef<T | null>(null);
|
||||
const [dimension, setDimension] = useState<IDimension>({
|
||||
width: 0,
|
||||
@@ -53,7 +53,14 @@ export const useContainerDimensions = <
|
||||
|
||||
useResizeObserver(target, (entry) => {
|
||||
const { inlineSize: width, blockSize: height } = entry.contentBoxSize[0];
|
||||
setDimension({ width, height });
|
||||
let difference = Math.abs(dimension.width - width);
|
||||
// Only adjust when width changed by a significant margin. This addresses the cornercase that sees
|
||||
// the dimensions toggle back and forward when the window is adjusted perfectly such that overflow
|
||||
// is trigger then immediable disabled because of a resize event then continues this loop endlessly.
|
||||
// the scrollbar size varies between platforms. Windows is apparently around 17 pixels.
|
||||
if (difference > sensitivityThreshold) {
|
||||
setDimension({ width, height });
|
||||
}
|
||||
});
|
||||
|
||||
return [target, dimension];
|
||||
|
||||
@@ -54,7 +54,11 @@ export const ScrapedStudioRow: React.FC<IScrapedStudioRow> = ({
|
||||
isDisabled={!isNew}
|
||||
onSelect={(items) => {
|
||||
if (onChangeFn) {
|
||||
onChangeFn(items[0]);
|
||||
const { id, ...data } = items[0];
|
||||
onChangeFn({
|
||||
...data,
|
||||
stored_id: id,
|
||||
});
|
||||
}
|
||||
}}
|
||||
values={selectValue}
|
||||
|
||||
@@ -264,7 +264,7 @@ export const Tagger: React.FC<ITaggerProps> = ({ scenes, queue }) => {
|
||||
<div>
|
||||
{filteredScenes.map((s, i) => (
|
||||
<Scene
|
||||
key={i}
|
||||
key={s.id}
|
||||
scene={s}
|
||||
searchResult={searchResults[s.id]}
|
||||
index={i}
|
||||
|
||||
@@ -47,7 +47,7 @@ Once migrated, these files can be deleted. The files can be optionally deleted d
|
||||
* Fixed `/stream` endpoint serving directory list. ([#3541](https://github.com/stashapp/stash/pull/3541))
|
||||
* Fixed error when querying with a large or unlimited page size. ([#3544](https://github.com/stashapp/stash/pull/3544))
|
||||
* Fixed sprites not being displayed for scenes with numeric-only hashes. ([#3513](https://github.com/stashapp/stash/pull/3513))
|
||||
* Fixed Save button being disabled when stting Tag image. ([#3509](https://github.com/stashapp/stash/pull/3509))
|
||||
* Fixed Save button being disabled when setting Tag image. ([#3509](https://github.com/stashapp/stash/pull/3509))
|
||||
* Fixed incorrect performer with identical name being matched when scraping from stash-box. ([#3488](https://github.com/stashapp/stash/pull/3488))
|
||||
* Fixed scene cover not being included when submitting file-less scenes to stash-box. ([#3465](https://github.com/stashapp/stash/pull/3465))
|
||||
* Fixed URL not being during stash-box scrape if the Studio URL is not set. ([#3439](https://github.com/stashapp/stash/pull/3439))
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
* Changed umask when creating config file to exclude user write (CVE-2024-32233) ([#4866](https://github.com/stashapp/stash/pull/4866))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* **[0.26.2]** Fixed issue where performer could not be created without disambiguation if a performer with the same name and populated disambiguation exists. ([#5019](https://github.com/stashapp/stash/pull/5019))
|
||||
* **[0.26.2]** Fix resize loop in grid views. ([#5004](https://github.com/stashapp/stash/pull/5004))
|
||||
* **[0.26.2]** Fix query field values duplicating in tagger view when scene list is updated. ([#5000](https://github.com/stashapp/stash/pull/5000))
|
||||
* **[0.26.2]** Fix identify clearing parent studio when merging studio field. ([#4993](https://github.com/stashapp/stash/pull/4993))
|
||||
* **[0.26.2]** Fix manually selected studio not being applied during scrape. ([#4953](https://github.com/stashapp/stash/pull/4953))
|
||||
* **[0.26.1]** Fixed identify task defaults not displaying correctly. ([#4931](https://github.com/stashapp/stash/pull/4931))
|
||||
* **[0.26.1]** Fixed issue where full hardware transcoding did not work where a filter was not required. ([#4934](https://github.com/stashapp/stash/pull/4934))
|
||||
* **[0.26.1]** Fixed new performer tags not displaying correctly in the performer scrape dialog. ([#4943](https://github.com/stashapp/stash/pull/4943))
|
||||
|
||||
@@ -15,7 +15,7 @@ Regex patterns can be added in the config file or from the UI.
|
||||
If you add manually to the config file a restart is needed while from the UI you just need to click the Save button.
|
||||
When added through the config file directly special care must be given to double escape the `\` character.
|
||||
|
||||
There are 2 sperate exclusion settings. One is for videos, another is for images/galleries.
|
||||
There are 2 separate exclusion settings. One is for videos, another is for images/galleries.
|
||||
|
||||
Some examples:
|
||||
|
||||
@@ -23,7 +23,7 @@ Some examples:
|
||||
- `"/\.[[:word:]]+/"` will exclude all hidden directories like `/.directoryname/`.
|
||||
- `"c:\\stash\\videos\\exclude"` will exclude specific Windows directory `c:\stash\videos\exclude`.
|
||||
- `"^/stash/videos/exclude/"` will exclude all directories that match `/stash/videos/exclude/` pattern.
|
||||
- `"\\\\stash\\network\\share\\excl\\"` will exlcude specific Windows network path `\\stash\network\share\excl\`.
|
||||
- `"\\\\stash\\network\\share\\excl\\"` will exclude specific Windows network path `\\stash\network\share\excl\`.
|
||||
|
||||
> **Note:** If a directory is excluded for images and videos, then the directory will be excluded from scans completely.
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
Stash works by cataloging your media using the paths that you provide. Once you have [configured](/settings?tab=library) the locations where your media is stored, you can click the Scan button in [`Settings -> Tasks`](/settings?tab=tasks) and stash will begin scanning and importing your media into its library.
|
||||
|
||||
For the best experience, it is recommmended that after a scan is finished, that video previews and sprites are generated. You can do this in [`Settings -> Tasks`](/settings?tab=tasks). Note that currently it is only possible to perform one task at a time and but there is a task queue, so the generate tasks should be performed after scan is complete.
|
||||
For the best experience, it is recommended that after a scan is finished, that video previews and sprites are generated. You can do this in [`Settings -> Tasks`](/settings?tab=tasks). Note that currently it is only possible to perform one task at a time and but there is a task queue, so the generate tasks should be performed after scan is complete.
|
||||
|
||||
Once your media is imported, you are ready to begin creating Performers, Studios and Tags, and curating your content!
|
||||
@@ -1,5 +1,5 @@
|
||||
This release introduces scraper and plugin management interfaces. This allows installing, updating and removing scrapers and plugins from the WebUI.
|
||||
|
||||
Default package sources have been automatically configured to point at the _stable_ branches of the `CommunityScrapers` and `CommunityScripts` respositories. These branches will correspond to the current stable version of stash.
|
||||
Default package sources have been automatically configured to point at the _stable_ branches of the `CommunityScrapers` and `CommunityScripts` repositories. These branches will correspond to the current stable version of stash.
|
||||
|
||||
**Note:** existing scrapers and plugins will _not_ be able to be managed using the management interface. It is recommended that any existing scrapers and plugins that are available from the community repositories are backed up and removed from the applicable `scrapers` or `plugins` directory, and reinstalled using the management UI.
|
||||
|
||||
Reference in New Issue
Block a user