diff --git a/pkg/file/fs.go b/pkg/file/fs.go index 8c09b0fe9..0a24aaa53 100644 --- a/pkg/file/fs.go +++ b/pkg/file/fs.go @@ -4,6 +4,8 @@ import ( "io" "io/fs" "os" + + "github.com/stashapp/stash/pkg/fsutil" ) // Opener provides an interface to open a file. @@ -26,6 +28,7 @@ type FS interface { 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. @@ -51,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) +} diff --git a/pkg/file/scan.go b/pkg/file/scan.go index 358ff7a27..1d7830f7e 100644 --- a/pkg/file/scan.go +++ b/pkg/file/scan.go @@ -740,7 +740,6 @@ 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 { missing = append(missing, other) @@ -749,6 +748,14 @@ func (s *scanJob) handleRename(ctx context.Context, f File, fp []Fingerprint) (F 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) + } } } diff --git a/pkg/file/zip.go b/pkg/file/zip.go index 3cf4a819c..1e61d340e 100644 --- a/pkg/file/zip.go +++ b/pkg/file/zip.go @@ -83,6 +83,10 @@ 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 } diff --git a/pkg/sqlite/file.go b/pkg/sqlite/file.go index 4c72d299e..c79002b11 100644 --- a/pkg/sqlite/file.go +++ b/pkg/sqlite/file.go @@ -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) {