diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/util/PathPatternResolver.java b/booklore-api/src/main/java/com/adityachandel/booklore/util/PathPatternResolver.java index ccbab241..f355c7bb 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/util/PathPatternResolver.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/util/PathPatternResolver.java @@ -266,6 +266,9 @@ public class PathPatternResolver { if (component.getBytes(StandardCharsets.UTF_8).length > MAX_FILESYSTEM_COMPONENT_BYTES) { component = truncatePathComponent(component, MAX_FILESYSTEM_COMPONENT_BYTES); } + while (component.endsWith(".")) { + component = component.substring(0, component.length() - 1); + } } if (i > 0) result.append("/"); diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/util/PathPatternResolverTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/util/PathPatternResolverTest.java index 83ae2783..55c70ab7 100644 --- a/booklore-api/src/test/java/com/adityachandel/booklore/util/PathPatternResolverTest.java +++ b/booklore-api/src/test/java/com/adityachandel/booklore/util/PathPatternResolverTest.java @@ -467,4 +467,47 @@ class PathPatternResolverTest { int byteLen = result.getBytes(StandardCharsets.UTF_8).length; assertTrue(byteLen <= 245, "Total filename bytes " + byteLen + " should be <= 245"); } + + @Test + @DisplayName("Should remove trailing dots from path components for Windows compatibility") + void testResolvePattern_removesTrailingDots() { + BookMetadata metadata = BookMetadata.builder() + .title("Book Title") + .authors(Set.of("Author Name Jr.")) + .build(); + + // Pattern: {authors}/{title} + String result = PathPatternResolver.resolvePattern(metadata, "{authors}/{title}", "original.pdf"); + + // Expected: Author Name Jr/Book Title.pdf + // Windows does not allow folder names ending in '.' + // So "Author Name Jr." should become "Author Name Jr" + + String[] components = result.split("/"); + assertTrue(components.length >= 1); + + String authorDir = components[0]; + assertFalse(authorDir.endsWith("."), "Directory name should not end with a dot: " + authorDir); + assertTrue(authorDir.equals("Author Name Jr"), "Expected 'Author Name Jr' but got '" + authorDir + "'"); + } + + @Test + @DisplayName("Should remove trailing dots from multiple path components") + void testResolvePattern_removesTrailingDotsFromMultipleComponents() { + BookMetadata metadata = BookMetadata.builder() + .title("Book Title.") + .seriesName("Series.") + .authors(Set.of("Author.")) + .build(); + + String result = PathPatternResolver.resolvePattern(metadata, "{authors}/{series}/{title}", "original.pdf"); + + String[] components = result.split("/"); + for (int i = 0; i < components.length - 1; i++) { // Check directories + assertFalse(components[i].endsWith("."), "Component " + i + " should not end with dot: " + components[i]); + } + + assertTrue(components[0].equals("Author")); + assertTrue(components[1].equals("Series")); + } }