1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package de.aikiit.fotorenamer.image;
17
18 import org.apache.logging.log4j.Level;
19 import org.apache.logging.log4j.LogManager;
20 import org.apache.logging.log4j.core.LogEvent;
21 import org.apache.logging.log4j.core.Logger;
22 import org.apache.logging.log4j.core.appender.AbstractAppender;
23 import org.apache.logging.log4j.core.config.Property;
24 import org.junit.jupiter.api.Test;
25 import org.junit.jupiter.api.io.TempDir;
26
27 import java.nio.file.FileAlreadyExistsException;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.concurrent.CopyOnWriteArrayList;
33
34 import static org.assertj.core.api.Assertions.assertThat;
35
36 class RemoveExifPrefixRenamerTest {
37
38 @TempDir
39 Path directory;
40
41 @Test
42 void roundTripRestoresOriginalFilenameAndContents() throws Exception {
43 Path original = copyImage("holiday.dat");
44 byte[] content = Files.readAllBytes(original);
45 var forward = new CreationDateFromExifImageRenamer(directory.toString());
46 assertThat(forward.renameFiles().renamed()).isEqualTo(1);
47 Path prefixed = directory.resolve("20110130_131102_holiday.dat");
48 assertThat(prefixed).exists();
49 assertThat(original).doesNotExist();
50
51 var reverse = new RemoveExifPrefixRenamer(directory.toString());
52
53 assertThat(prefixed).exists();
54 assertThat(reverse.renameFiles().renamed()).isEqualTo(1);
55 assertThat(prefixed).doesNotExist();
56 assertThat(Files.readAllBytes(original)).containsExactly(content);
57 }
58
59 @Test
60 void supportsLegacyPrefixesAndUnicodeNamesWithoutExtensions() throws Exception {
61 Path legacy = copyImage("20110130_1311_legacy.jpg");
62 Path unicode = copyImage("20110130_131102_Urlaub am See äß");
63 byte[] legacyContent = Files.readAllBytes(legacy);
64 byte[] unicodeContent = Files.readAllBytes(unicode);
65 var renamer = new RemoveExifPrefixRenamer(directory.toString());
66 assertThat(renamer.renameFiles()).isEqualTo(new RenameResult(2, 2, 2, 0, 0, false));
67 assertThat(Files.readAllBytes(directory.resolve("legacy.jpg"))).containsExactly(legacyContent);
68 assertThat(Files.readAllBytes(directory.resolve("Urlaub am See äß"))).containsExactly(unicodeContent);
69 }
70
71 @Test
72 void preservesUnchangedNamesAndEmptyTargetsAndReportsProgress() throws Exception {
73 List<String> names = List.of("plain.jpg", "photo_20110130_131102_test.jpg",
74 "20110130_13110_invalid.jpg", "20110130_131102_");
75 for (String name : names) {
76 copyImage(name);
77 }
78 var renamer = new RemoveExifPrefixRenamer(directory.toString());
79 List<Path> processed = new ArrayList<>();
80 assertThat(renamer.renameFiles(progress -> processed.add(progress.file()))).isEqualTo(new RenameResult(4, 4, 0, 4, 0, false));
81 assertThat(processed).hasSameSizeAs(names);
82 for (String name : names) {
83 assertThat(directory.resolve(name)).as(name).exists();
84 }
85 }
86
87 @Test
88 void removesOnlyOneLeadingPrefix() throws Exception {
89 Path image = copyImage("20110130_131102_20110129_1200_photo.bin");
90 byte[] original = Files.readAllBytes(image);
91 var renamer = new RemoveExifPrefixRenamer(directory.toString());
92 assertThat(renamer.renameFiles().renamed()).isEqualTo(1);
93 assertThat(Files.readAllBytes(directory.resolve("20110129_1200_photo.bin"))).containsExactly(original);
94 }
95
96 @Test
97 void ignoresNonImagesAndSubdirectoriesEvenWithMatchingPrefixes() throws Exception {
98 Path text = Files.writeString(directory.resolve("20110130_131102_notes.jpg"), "not an image");
99 Path folder = Files.createDirectory(directory.resolve("20110130_131102_folder"));
100 copyImage("20110130_131102_photo");
101 var renamer = new RemoveExifPrefixRenamer(directory.toString());
102 assertThat(renamer.renameFiles().renamed()).isEqualTo(1);
103 assertThat(directory.resolve("photo")).exists();
104 assertThat(Files.readString(text)).isEqualTo("not an image");
105 assertThat(folder).isDirectory();
106 }
107
108 @Test
109 void preservesExistingTargetsLogsCauseAndContinuesWithOtherImages() throws Exception {
110 Path source = copyImage("20110130_131102_photo.dat");
111 byte[] original = Files.readAllBytes(source);
112 Path target = Files.writeString(directory.resolve("photo.dat"), "keep this file");
113 copyImage("20110130_131102_other.dat");
114 var renamer = new RemoveExifPrefixRenamer(directory.toString());
115 Logger logger = (Logger) LogManager.getLogger(AbstractImageRenamer.class);
116 List<LogEvent> events = new CopyOnWriteArrayList<>();
117 AbstractAppender appender = new AbstractAppender("reverse-errors", null, null, false, Property.EMPTY_ARRAY) {
118 @Override
119 public void append(LogEvent event) {
120 events.add(event.toImmutable());
121 }
122 };
123 appender.start();
124 logger.addAppender(appender);
125 try {
126 assertThat(renamer.renameFiles()).isEqualTo(new RenameResult(2, 2, 1, 0, 1, false));
127 assertThat(events.stream().anyMatch(event -> event.getLevel() == Level.ERROR
128 && event.getThrown() instanceof FileAlreadyExistsException
129 && event.getMessage().getFormattedMessage().contains(source.toString())
130 && event.getMessage().getFormattedMessage().contains(target.toString()))).isTrue();
131 } finally {
132 logger.removeAppender(appender);
133 appender.stop();
134 }
135 assertThat(Files.readAllBytes(source)).containsExactly(original);
136 assertThat(Files.readString(target)).isEqualTo("keep this file");
137 assertThat(directory.resolve("other.dat")).exists();
138 }
139
140 @Test
141 void sourceReplacedByDirectoryIsLeftUntouched() throws Exception {
142 Path source = copyImage("20110130_131102_replaced");
143 var renamer = new RemoveExifPrefixRenamer(directory.toString());
144 Files.delete(source);
145 Files.createDirectory(source);
146 List<Path> processed = new ArrayList<>();
147 assertThat(renamer.renameFiles(progress -> processed.add(progress.file()))).isEqualTo(new RenameResult(1, 1, 0, 1, 0, false));
148 assertThat(processed).containsExactly(source);
149 assertThat(source).isDirectory();
150 assertThat(directory.resolve("replaced")).doesNotExist();
151 }
152
153 @Test
154 void missingSourceDoesNotAbortRemainingMoves() throws Exception {
155 Path missing = copyImage("20110130_131102_missing");
156 copyImage("20110130_131102_existing");
157 var renamer = new RemoveExifPrefixRenamer(directory.toString());
158 Files.delete(missing);
159 assertThat(renamer.renameFiles()).isEqualTo(new RenameResult(2, 2, 1, 0, 1, false));
160 assertThat(directory.resolve("existing")).exists();
161 assertThat(directory.resolve("missing")).doesNotExist();
162 }
163
164 private Path copyImage(String name) throws Exception {
165 Path target = directory.resolve(name);
166 try (var input = getClass().getResourceAsStream("/data/IMG_7559_mini.JPG")) {
167 assertThat(input).isNotNull();
168 Files.copy(input, target);
169 }
170 return target;
171 }
172 }