mirror of
https://github.com/stashapp/stash.git
synced 2026-06-11 07:41:08 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02c2ad3f58 | ||
|
|
479ebfc88d | ||
|
|
1c92336798 | ||
|
|
5fae3cf127 | ||
|
|
47395ce13f | ||
|
|
091950615e | ||
|
|
4db0e48f73 |
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -476,7 +476,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
|
||||
setScraper(undefined);
|
||||
onSceneSelected(s);
|
||||
}}
|
||||
name={formik.values.title || ""}
|
||||
name={formik.values.title || objectTitle(scene) || ""}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user