Add rescan option to overflow dropdown (#1119)

* Make scan options optional
* Add scene rescan
* Add image rescan
* Add gallery rescan
* Add changelog
This commit is contained in:
WithoutPants
2021-02-23 12:56:01 +11:00
committed by GitHub
parent 7fbb92d071
commit f7a8899d90
10 changed files with 107 additions and 12 deletions

View File

@@ -33,15 +33,15 @@ input GeneratePreviewOptionsInput {
input ScanMetadataInput { input ScanMetadataInput {
paths: [String!] paths: [String!]
"""Set name, date, details from metadata (if present)""" """Set name, date, details from metadata (if present)"""
useFileMetadata: Boolean! useFileMetadata: Boolean
"""Strip file extension from title""" """Strip file extension from title"""
stripFileExtension: Boolean! stripFileExtension: Boolean
"""Generate previews during scan""" """Generate previews during scan"""
scanGeneratePreviews: Boolean! scanGeneratePreviews: Boolean
"""Generate image previews during scan""" """Generate image previews during scan"""
scanGenerateImagePreviews: Boolean! scanGenerateImagePreviews: Boolean
"""Generate sprites during scan""" """Generate sprites during scan"""
scanGenerateSprites: Boolean! scanGenerateSprites: Boolean
} }
input CleanMetadataInput { input CleanMetadataInput {

View File

@@ -59,6 +59,13 @@ func ZipFilename(zipFilename, filenameInZip string) string {
return zipFilename + zipSeparator + filenameInZip return zipFilename + zipSeparator + filenameInZip
} }
// IsZipPath returns true if the path includes the zip separator byte,
// indicating it is within a zip file.
// TODO - this should be moved to utils
func IsZipPath(p string) bool {
return strings.Contains(p, zipSeparator)
}
type imageReadCloser struct { type imageReadCloser struct {
src io.ReadCloser src io.ReadCloser
zrc *zip.ReadCloser zrc *zip.ReadCloser

View File

@@ -215,13 +215,13 @@ func (s *singleton) Scan(input models.ScanMetadataInput) {
task := ScanTask{ task := ScanTask{
TxnManager: s.TxnManager, TxnManager: s.TxnManager,
FilePath: path, FilePath: path,
UseFileMetadata: input.UseFileMetadata, UseFileMetadata: utils.IsTrue(input.UseFileMetadata),
StripFileExtension: input.StripFileExtension, StripFileExtension: utils.IsTrue(input.StripFileExtension),
fileNamingAlgorithm: fileNamingAlgo, fileNamingAlgorithm: fileNamingAlgo,
calculateMD5: calculateMD5, calculateMD5: calculateMD5,
GeneratePreview: input.ScanGeneratePreviews, GeneratePreview: utils.IsTrue(input.ScanGeneratePreviews),
GenerateImagePreview: input.ScanGenerateImagePreviews, GenerateImagePreview: utils.IsTrue(input.ScanGenerateImagePreviews),
GenerateSprite: input.ScanGenerateSprites, GenerateSprite: utils.IsTrue(input.ScanGenerateSprites),
} }
go task.Start(&wg) go task.Start(&wg)

View File

@@ -1044,6 +1044,12 @@ func walkFilesToScan(s *models.StashConfig, f filepath.WalkFunc) error {
excludeVidRegex := generateRegexps(config.GetExcludes()) excludeVidRegex := generateRegexps(config.GetExcludes())
excludeImgRegex := generateRegexps(config.GetImageExcludes()) excludeImgRegex := generateRegexps(config.GetImageExcludes())
// don't scan zip images directly
if image.IsZipPath(s.Path) {
logger.Warnf("Cannot rescan zip image %s. Rescan zip gallery instead.", s.Path)
return nil
}
generatedPath := config.GetGeneratedPath() generatedPath := config.GetGeneratedPath()
return utils.SymWalk(s.Path, func(path string, info os.FileInfo, err error) error { return utils.SymWalk(s.Path, func(path string, info os.FileInfo, err error) error {

View File

@@ -7,3 +7,8 @@ func Btoi(b bool) int {
} }
return 0 return 0
} }
// IsTrue returns true if the bool pointer is not nil and true.
func IsTrue(b *bool) bool {
return b != nil && *b
}

View File

@@ -8,6 +8,7 @@ import V021 from "./versions/v021.md";
import V030 from "./versions/v030.md"; import V030 from "./versions/v030.md";
import V040 from "./versions/v040.md"; import V040 from "./versions/v040.md";
import V050 from "./versions/v050.md"; import V050 from "./versions/v050.md";
import V060 from "./versions/v060.md";
import { MarkdownPage } from "../Shared/MarkdownPage"; import { MarkdownPage } from "../Shared/MarkdownPage";
const Changelog: React.FC = () => { const Changelog: React.FC = () => {
@@ -37,11 +38,19 @@ const Changelog: React.FC = () => {
<> <>
<h1 className="mb-4">Changelog:</h1> <h1 className="mb-4">Changelog:</h1>
<Version <Version
version={stashVersion || "v0.5.0"} version={stashVersion || "v0.6.0"}
date={buildDate} date={buildDate}
openState={openState} openState={openState}
setOpenState={setVersionOpenState} setOpenState={setVersionOpenState}
defaultOpen defaultOpen
>
<MarkdownPage page={V060} />
</Version>
<Version
version="v0.5.0"
date="2021-02-23"
openState={openState}
setOpenState={setVersionOpenState}
> >
<MarkdownPage page={V050} /> <MarkdownPage page={V050} />
</Version> </Version>

View File

@@ -0,0 +1,2 @@
### 🎨 Improvements
* Added Rescan button to scene, image, gallery details overflow button.

View File

@@ -1,7 +1,11 @@
import { Tab, Nav, Dropdown } from "react-bootstrap"; import { Tab, Nav, Dropdown } from "react-bootstrap";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useParams, useHistory, Link } from "react-router-dom"; import { useParams, useHistory, Link } from "react-router-dom";
import { useFindGallery, useGalleryUpdate } from "src/core/StashService"; import {
mutateMetadataScan,
useFindGallery,
useGalleryUpdate,
} from "src/core/StashService";
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
import { TextUtils } from "src/utils"; import { TextUtils } from "src/utils";
import * as Mousetrap from "mousetrap"; import * as Mousetrap from "mousetrap";
@@ -60,6 +64,18 @@ export const Gallery: React.FC = () => {
} }
}; };
async function onRescan() {
if (!gallery || !gallery.path) {
return;
}
await mutateMetadataScan({
paths: [gallery.path],
});
Toast.success({ content: "Rescanning image" });
}
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
function onDeleteDialogClosed(deleted: boolean) { function onDeleteDialogClosed(deleted: boolean) {
@@ -92,6 +108,15 @@ export const Gallery: React.FC = () => {
<Icon icon="ellipsis-v" /> <Icon icon="ellipsis-v" />
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="bg-secondary text-white"> <Dropdown.Menu className="bg-secondary text-white">
{gallery?.path ? (
<Dropdown.Item
key="rescan"
className="bg-secondary text-white"
onClick={() => onRescan()}
>
Rescan
</Dropdown.Item>
) : undefined}
<Dropdown.Item <Dropdown.Item
key="delete-gallery" key="delete-gallery"
className="bg-secondary text-white" className="bg-secondary text-white"

View File

@@ -7,6 +7,7 @@ import {
useImageDecrementO, useImageDecrementO,
useImageResetO, useImageResetO,
useImageUpdate, useImageUpdate,
mutateMetadataScan,
} from "src/core/StashService"; } from "src/core/StashService";
import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared";
import { useToast } from "src/hooks"; import { useToast } from "src/hooks";
@@ -43,6 +44,18 @@ export const Image: React.FC = () => {
const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState<boolean>(false);
async function onRescan() {
if (!image) {
return;
}
await mutateMetadataScan({
paths: [image.path],
});
Toast.success({ content: "Rescanning image" });
}
const onOrganizedClick = async () => { const onOrganizedClick = async () => {
try { try {
setOrganizedLoading(true); setOrganizedLoading(true);
@@ -121,6 +134,13 @@ export const Image: React.FC = () => {
<Icon icon="ellipsis-v" /> <Icon icon="ellipsis-v" />
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="bg-secondary text-white"> <Dropdown.Menu className="bg-secondary text-white">
<Dropdown.Item
key="rescan"
className="bg-secondary text-white"
onClick={() => onRescan()}
>
Rescan
</Dropdown.Item>
<Dropdown.Item <Dropdown.Item
key="delete-image" key="delete-image"
className="bg-secondary text-white" className="bg-secondary text-white"

View File

@@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react";
import { useParams, useLocation, useHistory, Link } from "react-router-dom"; import { useParams, useLocation, useHistory, Link } from "react-router-dom";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { import {
mutateMetadataScan,
useFindScene, useFindScene,
useSceneIncrementO, useSceneIncrementO,
useSceneDecrementO, useSceneDecrementO,
@@ -51,6 +52,7 @@ export const Scene: React.FC = () => {
error: streamableError, error: streamableError,
loading: streamableLoading, loading: streamableLoading,
} = useSceneStreams(id); } = useSceneStreams(id);
const [oLoading, setOLoading] = useState(false); const [oLoading, setOLoading] = useState(false);
const [incrementO] = useSceneIncrementO(scene?.id ?? "0"); const [incrementO] = useSceneIncrementO(scene?.id ?? "0");
const [decrementO] = useSceneDecrementO(scene?.id ?? "0"); const [decrementO] = useSceneDecrementO(scene?.id ?? "0");
@@ -130,6 +132,18 @@ export const Scene: React.FC = () => {
setTimestamp(marker.seconds); setTimestamp(marker.seconds);
} }
async function onRescan() {
if (!scene) {
return;
}
await mutateMetadataScan({
paths: [scene.path],
});
Toast.success({ content: "Rescanning scene" });
}
async function onGenerateScreenshot(at?: number) { async function onGenerateScreenshot(at?: number) {
if (!scene) { if (!scene) {
return; return;
@@ -184,6 +198,13 @@ export const Scene: React.FC = () => {
<Icon icon="ellipsis-v" /> <Icon icon="ellipsis-v" />
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu className="bg-secondary text-white"> <Dropdown.Menu className="bg-secondary text-white">
<Dropdown.Item
key="rescan"
className="bg-secondary text-white"
onClick={() => onRescan()}
>
Rescan
</Dropdown.Item>
<Dropdown.Item <Dropdown.Item
key="generate" key="generate"
className="bg-secondary text-white" className="bg-secondary text-white"