fix(move): move every originals file of a photo, not just the primary
Videos, Live Photos, and RAW+JPG pairs keep several files under Root "/". The old movePhotoFiles moved only the primary (often the poster JPG), orphaning the .mov: PhotoPrism then saw the photo as moved (dropped from the grid) while the video stayed behind and broke. Move the whole originals group under one shared stem (new uniqueStem helper) so siblings re-stack after reindex; fail the photo and report it if any sibling can't move. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -108,6 +108,37 @@ func uniqueName(destDir, basename string) (abs, name string, ok bool) {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// uniqueStem finds a base name (extension stripped) that is free for *every*
|
||||
// extension in `exts` under destDir, appending `-1`, `-2`, … on collision —
|
||||
// the multi-file analogue of uniqueName. Moving a photo's originals siblings
|
||||
// (e.g. IMG_1234.JPG + IMG_1234.MOV) under a single shared stem keeps
|
||||
// PhotoPrism stacking them as one photo after reindex; picking the stem once
|
||||
// for the whole group is what stops the video from being orphaned under a
|
||||
// differently-suffixed name than its poster. Caps at 1000 attempts to match
|
||||
// uniqueName. The passed extensions keep their on-disk case (we compare
|
||||
// case-sensitively via os.Stat, which is correct on the case-sensitive
|
||||
// volumes PhotoPrism targets).
|
||||
func uniqueStem(destDir, primaryBase string, exts []string) (stem string, ok bool) {
|
||||
base := strings.TrimSuffix(primaryBase, filepath.Ext(primaryBase))
|
||||
for i := 0; i < 1000; i++ {
|
||||
candidate := base
|
||||
if i > 0 {
|
||||
candidate = base + "-" + itoa(i)
|
||||
}
|
||||
free := true
|
||||
for _, ext := range exts {
|
||||
if _, err := os.Stat(filepath.Join(destDir, candidate+ext)); !errors.Is(err, os.ErrNotExist) {
|
||||
free = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if free {
|
||||
return candidate, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// itoa is the tiny stdlib-free formatter we use inside hot loops.
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
|
||||
Reference in New Issue
Block a user