From cf391172a1f64c50841a7484ef21ad5813ae9312 Mon Sep 17 00:00:00 2001 From: Jan Bader Date: Sun, 13 Sep 2026 16:45:35 +0200 Subject: [PATCH] fix: prevent overwrite when document has multiple files --- main.go | 45 +++++++++++++++++++++++++++++++++++++++++---- main_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 4066fca..73e3ec5 100644 --- a/main.go +++ b/main.go @@ -323,10 +323,16 @@ func handleExistingFile(ctx context.Context, client *DocspellClient, cfg config, return fmt.Errorf("create directory: %w", err) } - subfolder := fmt.Sprintf("%s %s - %s.%s", date.Format("20060102"), corr, itemName, extension) - newPath := filepath.Join(curDir, subfolder) - - if err := MoveFile(fileExistsResponse.File, newPath); err != nil { + baseName := fmt.Sprintf("%s %s - %s", date.Format("20060102"), corr, itemName) + newPath, alreadyArchived, err := archiveDestination(curDir, baseName, extension, len(itemDetails.Attachments), fileExistsResponse.File) + if err != nil { + return err + } + if alreadyArchived { + if err := os.Remove(fileExistsResponse.File); err != nil { + return fmt.Errorf("remove duplicate source file: %w", err) + } + } else if err := MoveFile(fileExistsResponse.File, newPath); err != nil { return fmt.Errorf("move file: %w", err) } @@ -334,6 +340,37 @@ func handleExistingFile(ctx context.Context, client *DocspellClient, cfg config, return nil } +func archiveDestination(directory, baseName, extension string, attachmentCount int, sourcePath string) (string, bool, error) { + if attachmentCount < 1 { + attachmentCount = 1 + } + for index := 1; ; index++ { + suffix := "" + if attachmentCount > 1 || index > 1 { + suffix = fmt.Sprintf(".%d", index) + } + destination := filepath.Join(directory, baseName+suffix+"."+extension) + _, err := os.Stat(destination) + if os.IsNotExist(err) { + return destination, false, nil + } + if err != nil { + return "", false, fmt.Errorf("inspect archive destination: %w", err) + } + sourceHash, err := fileSHA256(sourcePath) + if err != nil { + return "", false, fmt.Errorf("hash source file: %w", err) + } + destinationHash, err := fileSHA256(destination) + if err != nil { + return "", false, fmt.Errorf("hash archive destination: %w", err) + } + if sourceHash == destinationHash { + return destination, true, nil + } + } +} + func validateConfig(cfg config) { if len(cfg.ImportDirectories) == 0 || cfg.ArchiveDirectory == "" { fmt.Println("FATAL Parameter missing") diff --git a/main_test.go b/main_test.go index 51c7da0..2b8afe5 100644 --- a/main_test.go +++ b/main_test.go @@ -19,3 +19,41 @@ func TestConfigFilePathUsesXDGConfigDir(t *testing.T) { t.Fatalf("expected %q, got %q", want, got) } } + +func TestArchiveDestinationIndexesMultipleAttachmentsAndPreservesDistinctFiles(t *testing.T) { + directory := t.TempDir() + sourcePath := filepath.Join(t.TempDir(), "source.pdf") + if err := os.WriteFile(sourcePath, []byte("source"), 0644); err != nil { + t.Fatal(err) + } + + got, alreadyArchived, err := archiveDestination(directory, "Document", "pdf", 2, sourcePath) + if err != nil { + t.Fatal(err) + } + if alreadyArchived || got != filepath.Join(directory, "Document.1.pdf") { + t.Fatalf("expected first indexed destination, got %q (already archived: %t)", got, alreadyArchived) + } + + if err := os.WriteFile(got, []byte("different"), 0644); err != nil { + t.Fatal(err) + } + got, alreadyArchived, err = archiveDestination(directory, "Document", "pdf", 2, sourcePath) + if err != nil { + t.Fatal(err) + } + if alreadyArchived || got != filepath.Join(directory, "Document.2.pdf") { + t.Fatalf("expected second indexed destination, got %q (already archived: %t)", got, alreadyArchived) + } + + if err := os.WriteFile(got, []byte("source"), 0644); err != nil { + t.Fatal(err) + } + got, alreadyArchived, err = archiveDestination(directory, "Document", "pdf", 2, sourcePath) + if err != nil { + t.Fatal(err) + } + if !alreadyArchived || got != filepath.Join(directory, "Document.2.pdf") { + t.Fatalf("expected matching indexed destination, got %q (already archived: %t)", got, alreadyArchived) + } +}