Compare commits

..

7 Commits

Author SHA1 Message Date
WithoutPants
02c2ad3f58 Update changelog 2022-10-25 11:42:27 +11:00
WithoutPants
479ebfc88d Reimplement case-insensitivity move bug fix (#3047)
* Use eq for FindByPath for case sensitivity
* Handle case sensitive moves
2022-10-25 11:37:54 +11:00
WithoutPants
1c92336798 Fix symlink size calculation (#3046) 2022-10-25 10:57:37 +11:00
WithoutPants
5fae3cf127 Update changelog 2022-10-24 14:40:51 +11:00
WithoutPants
47395ce13f Use basename as title if empty when scraping by fragment (#3040)
* Fallback to file basename if title empty in scrape
* Populate dialog from basename if title empty
2022-10-24 14:36:22 +11:00
WithoutPants
091950615e Ignore non-existing scenes in fingerprint submits (#3039) 2022-10-24 10:26:21 +11:00
DingDongSoLong4
4db0e48f73 Fix zip gallery renaming (#3036) 2022-10-24 09:38:02 +11:00
9 changed files with 93 additions and 41 deletions

View File

@@ -4,6 +4,8 @@ import (
"io"
"io/fs"
"os"
"github.com/stashapp/stash/pkg/fsutil"
)
// Opener provides an interface to open a file.
@@ -22,14 +24,20 @@ func (o *fsOpener) Open() (io.ReadCloser, error) {
// FS represents a file system.
type FS interface {
Stat(name string) (fs.FileInfo, error)
Lstat(name string) (fs.FileInfo, error)
Open(name string) (fs.ReadDirFile, error)
OpenZip(name string) (*ZipFS, error)
IsPathCaseSensitive(path string) (bool, error)
}
// OsFS is a file system backed by the OS.
type OsFS struct{}
func (f *OsFS) Stat(name string) (fs.FileInfo, error) {
return os.Stat(name)
}
func (f *OsFS) Lstat(name string) (fs.FileInfo, error) {
return os.Lstat(name)
}
@@ -46,3 +54,7 @@ func (f *OsFS) OpenZip(name string) (*ZipFS, error) {
return newZipFS(f, name, info)
}
func (f *OsFS) IsPathCaseSensitive(path string) (bool, error) {
return fsutil.IsFsPathCaseSensitive(path)
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"sync"
@@ -129,9 +130,8 @@ func (s *Scanner) Scan(ctx context.Context, handlers []Handler, options ScanOpti
type scanFile struct {
*BaseFile
fs FS
info fs.FileInfo
zipFile *scanFile
fs FS
info fs.FileInfo
}
func (s *scanJob) withTxn(ctx context.Context, fn func(ctx context.Context) error) error {
@@ -211,6 +211,19 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
return fmt.Errorf("reading info for %q: %w", path, err)
}
var size int64
// #2196/#3042 - replace size with target size if file is a symlink
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
targetInfo, err := f.Stat(path)
if err != nil {
return fmt.Errorf("reading info for symlink %q: %w", path, err)
}
size = targetInfo.Size()
} else {
size = info.Size()
}
if !s.acceptEntry(ctx, path, info) {
if info.IsDir() {
return fs.SkipDir
@@ -226,13 +239,19 @@ func (s *scanJob) queueFileFunc(ctx context.Context, f FS, zipFile *scanFile) fs
},
Path: path,
Basename: filepath.Base(path),
Size: info.Size(),
Size: size,
},
fs: f,
info: info,
// there is no guarantee that the zip file has been scanned
// so we can't just plug in the id.
zipFile: zipFile,
}
if zipFile != nil {
zipFileID, err := s.getZipFileID(ctx, zipFile)
if err != nil {
return err
}
ff.ZipFileID = zipFileID
ff.ZipFile = zipFile
}
if info.IsDir() {
@@ -348,7 +367,7 @@ func (s *scanJob) processQueue(ctx context.Context) error {
func (s *scanJob) incrementProgress(f scanFile) {
// don't increment for files inside zip files since these aren't
// counted during the initial walking
if s.ProgressReports != nil && f.zipFile == nil {
if s.ProgressReports != nil && f.ZipFile == nil {
s.ProgressReports.Increment()
}
}
@@ -453,23 +472,12 @@ func (s *scanJob) onNewFolder(ctx context.Context, file scanFile) (*Folder, erro
now := time.Now()
toCreate := &Folder{
DirEntry: DirEntry{
ModTime: file.ModTime,
},
DirEntry: file.DirEntry,
Path: file.Path,
CreatedAt: now,
UpdatedAt: now,
}
zipFileID, err := s.getZipFileID(ctx, file.zipFile)
if err != nil {
return nil, err
}
if zipFileID != nil {
toCreate.ZipFileID = zipFileID
}
dir := filepath.Dir(file.Path)
if dir != "." {
parentFolderID, err := s.getFolderID(ctx, dir)
@@ -601,15 +609,6 @@ func (s *scanJob) onNewFile(ctx context.Context, f scanFile) (File, error) {
baseFile.ParentFolderID = *parentFolderID
zipFileID, err := s.getZipFileID(ctx, f.zipFile)
if err != nil {
return nil, err
}
if zipFileID != nil {
baseFile.ZipFileID = zipFileID
}
const useExisting = false
fp, err := s.calculateFingerprints(f.fs, baseFile, path, useExisting)
if err != nil {
@@ -741,14 +740,22 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F
for _, other := range others {
// if file does not exist, then update it to the new path
// TODO - handle #1426 scenario
fs, err := s.getFileFS(other.Base())
if err != nil {
return nil, fmt.Errorf("getting FS for %q: %w", other.Base().Path, err)
missing = append(missing, other)
continue
}
if _, err := fs.Lstat(other.Base().Path); err != nil {
missing = append(missing, other)
} else if strings.EqualFold(f.Base().Path, other.Base().Path) {
// #1426 - if file exists but is a case-insensitive match for the
// original filename, and the filesystem is case-insensitive
// then treat it as a move
if caseSensitive, _ := fs.IsPathCaseSensitive(other.Base().Path); !caseSensitive {
// treat as a move
missing = append(missing, other)
}
}
}

View File

@@ -65,7 +65,7 @@ func (f *ZipFS) rel(name string) (string, error) {
return relName, nil
}
func (f *ZipFS) Lstat(name string) (fs.FileInfo, error) {
func (f *ZipFS) Stat(name string) (fs.FileInfo, error) {
reader, err := f.Open(name)
if err != nil {
return nil, err
@@ -75,10 +75,18 @@ func (f *ZipFS) Lstat(name string) (fs.FileInfo, error) {
return reader.Stat()
}
func (f *ZipFS) Lstat(name string) (fs.FileInfo, error) {
return f.Stat(name)
}
func (f *ZipFS) OpenZip(name string) (*ZipFS, error) {
return nil, errZipFSOpenZip
}
func (f *ZipFS) IsPathCaseSensitive(path string) (bool, error) {
return true, nil
}
type zipReadDirFile struct {
fs.File
}

View File

@@ -2,6 +2,7 @@ package models
import (
"context"
"path/filepath"
"strconv"
"time"
@@ -128,7 +129,7 @@ func (g Gallery) GetTitle() string {
return g.Title
}
return g.Path
return filepath.Base(g.Path)
}
// DisplayName returns a display name for the scene for logging purposes.

View File

@@ -319,9 +319,12 @@ func sceneToUpdateInput(scene *models.Scene) models.SceneUpdateInput {
return nil
}
// fallback to file basename if title is empty
title := scene.GetTitle()
return models.SceneUpdateInput{
ID: strconv.Itoa(scene.ID),
Title: &scene.Title,
Title: &title,
Details: &scene.Details,
URL: &scene.URL,
Date: dateToStringPtr(scene.Date),
@@ -338,9 +341,12 @@ func galleryToUpdateInput(gallery *models.Gallery) models.GalleryUpdateInput {
return nil
}
// fallback to file basename if title is empty
title := gallery.GetTitle()
return models.GalleryUpdateInput{
ID: strconv.Itoa(gallery.ID),
Title: &gallery.Title,
Title: &title,
Details: &gallery.Details,
URL: &gallery.URL,
Date: dateToStringPtr(gallery.Date),

View File

@@ -3,7 +3,9 @@ package stashbox
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
@@ -227,8 +229,9 @@ func (c Client) SubmitStashBoxFingerprints(ctx context.Context, sceneIDs []strin
qb := c.repository.Scene
for _, sceneID := range ids {
// TODO - Find should return an appropriate not found error
scene, err := qb.Find(ctx, sceneID)
if err != nil {
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}

View File

@@ -586,10 +586,20 @@ func (qb *FileStore) FindByPath(ctx context.Context, p string) (file.File, error
table := qb.table()
folderTable := folderTableMgr.table
q := qb.selectDataset().Prepared(true).Where(
folderTable.Col("path").Like(dirName),
table.Col("basename").Like(basename),
)
// like uses case-insensitive matching. Only use like if wildcards are used
q := qb.selectDataset().Prepared(true)
if strings.Contains(basename, "%") || strings.Contains(dirName, "%") {
q = q.Where(
folderTable.Col("path").Like(dirName),
table.Col("basename").Like(basename),
)
} else {
q = q.Where(
folderTable.Col("path").Eq(dirName),
table.Col("basename").Eq(basename),
)
}
ret, err := qb.get(ctx, q)
if err != nil && !errors.Is(err, sql.ErrNoRows) {

View File

@@ -476,7 +476,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
setScraper(undefined);
onSceneSelected(s);
}}
name={formik.values.title || ""}
name={formik.values.title || objectTitle(scene) || ""}
/>
);
};

View File

@@ -28,6 +28,11 @@ After migrating, please run a scan on your entire library to populate missing da
* Moved Changelogs to Settings page. ([#2726](https://github.com/stashapp/stash/pull/2726))
### 🐛 Bug fixes
* **[0.17.2]** Fix file rename detection on case-insensitive file systems. ([#3047](https://github.com/stashapp/stash/pull/3047))
* **[0.17.2]** Fix size calculation for symlinks. ([#3046](https://github.com/stashapp/stash/pull/3046))
* **[0.17.2]** Use file base name as title if title is empty in scraper operations. ([#3040](https://github.com/stashapp/stash/pull/3040))
* **[0.17.2]** Fix error when submitting fingerprints from deleted scene. ([#3039](https://github.com/stashapp/stash/pull/3039))
* **[0.17.2]** Fix moved zip file creating duplicate galleries. ([#3036](https://github.com/stashapp/stash/pull/3036))
* **[0.17.1]** Fix Windows exporting incompatible zip files. ([#3022](https://github.com/stashapp/stash/pull/3022))
* **[0.17.1]** Fix migration error handling various NULL values. ([#3021](https://github.com/stashapp/stash/pull/3021))
* **[0.17.1]** Updated translations missed during release.