-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add feature relinking moved file #10954
Changes from all commits
27f0651
2d94893
533eaa0
ca0d08b
dc57b2b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package org.jabref.cli; | ||
|
||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
|
@@ -188,7 +189,7 @@ private Optional<ParserResult> importFile(Path file, String importFormat) { | |
} | ||
} | ||
|
||
public void processArguments() { | ||
public void processArguments() throws FileNotFoundException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add JavaDoc It is strange that upon startup of JabRef something is happening. The issue itself has two headings "First implementation" and "Follow-up implementation". I cannot relate something happening on startup at each of the headings. Maybe you can explain? |
||
uiCommands.clear(); | ||
|
||
if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { | ||
|
@@ -711,7 +712,7 @@ private void resetPreferences(String value) { | |
} | ||
} | ||
|
||
private void automaticallySetFileLinks(List<ParserResult> loaded) { | ||
private void automaticallySetFileLinks(List<ParserResult> loaded) throws FileNotFoundException { | ||
for (ParserResult parserResult : loaded) { | ||
BibDatabase database = parserResult.getDatabase(); | ||
LOGGER.info(Localization.lang("Automatically setting file links")); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package org.jabref.gui.externalfiles; | ||
|
||
import java.io.FileNotFoundException; | ||
import java.io.IOException; | ||
import java.nio.file.FileVisitOption; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
|
@@ -30,23 +32,25 @@ | |
|
||
public class AutoSetFileLinksUtil { | ||
|
||
record RelinkedResults(List<LinkedFile> relinkedFiles, List<String> exceptions) { } | ||
|
||
public static class LinkFilesResult { | ||
private final List<BibEntry> changedEntries = new ArrayList<>(); | ||
private final List<IOException> fileExceptions = new ArrayList<>(); | ||
private final List<String> fileExceptions = new ArrayList<String>(); | ||
|
||
protected void addBibEntry(BibEntry bibEntry) { | ||
changedEntries.add(bibEntry); | ||
} | ||
|
||
protected void addFileException(IOException exception) { | ||
protected void addFileException(String exception) { | ||
fileExceptions.add(exception); | ||
} | ||
|
||
public List<BibEntry> getChangedEntries() { | ||
return changedEntries; | ||
} | ||
|
||
public List<IOException> getFileExceptions() { | ||
public List<String> getFileExceptions() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why reduction to weaker type? |
||
return fileExceptions; | ||
} | ||
} | ||
|
@@ -66,7 +70,7 @@ private AutoSetFileLinksUtil(List<Path> directories, FilePreferences filePrefere | |
this.filePreferences = filePreferences; | ||
} | ||
|
||
public LinkFilesResult linkAssociatedFiles(List<BibEntry> entries, NamedCompound ce) { | ||
public LinkFilesResult linkAssociatedFiles(List<BibEntry> entries, NamedCompound ce) throws FileNotFoundException { | ||
LinkFilesResult result = new LinkFilesResult(); | ||
|
||
for (BibEntry entry : entries) { | ||
|
@@ -75,7 +79,7 @@ public LinkFilesResult linkAssociatedFiles(List<BibEntry> entries, NamedCompound | |
try { | ||
linkedFiles = findAssociatedNotLinkedFiles(entry); | ||
} catch (IOException e) { | ||
result.addFileException(e); | ||
result.addFileException(String.valueOf(e)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please describe, why this change seems necessary? |
||
LOGGER.error("Problem finding files", e); | ||
} | ||
|
||
|
@@ -94,10 +98,18 @@ public LinkFilesResult linkAssociatedFiles(List<BibEntry> entries, NamedCompound | |
entry.addFile(linkedFile); | ||
}); | ||
} | ||
|
||
if (changed) { | ||
result.addBibEntry(entry); | ||
} | ||
// Run Relinking Process | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment does not say anything... You can extract a method to group statements together. The algorithm itself should be explained |
||
RelinkedResults relink = relinkingFiles(entry.getFiles()); | ||
entry.setFiles(relink.relinkedFiles); | ||
if (!relink.relinkedFiles().isEmpty()) { | ||
result.addBibEntry(entry); | ||
} | ||
for (String e : (relink.exceptions)) { | ||
result.addFileException(e); | ||
} | ||
} | ||
} | ||
return result; | ||
|
@@ -127,16 +139,60 @@ public List<LinkedFile> findAssociatedNotLinkedFiles(BibEntry entry) throws IOEx | |
|
||
if (!fileAlreadyLinked) { | ||
Optional<ExternalFileType> type = FileUtil.getFileExtension(foundFile) | ||
.map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, filePreferences)) | ||
.orElse(Optional.of(new UnknownExternalFileType(""))); | ||
.map(extension -> ExternalFileTypes.getExternalFileTypeByExt(extension, filePreferences)) | ||
.orElse(Optional.of(new UnknownExternalFileType(""))); | ||
|
||
String strType = type.isPresent() ? type.get().getName() : ""; | ||
Path relativeFilePath = FileUtil.relativize(foundFile, directories); | ||
LinkedFile linkedFile = new LinkedFile("", relativeFilePath, strType); | ||
linkedFiles.add(linkedFile); | ||
} | ||
} | ||
|
||
return linkedFiles; | ||
} | ||
|
||
public RelinkedResults relinkingFiles(List<LinkedFile> listlinked) throws FileNotFoundException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method names are imperative normally. Rename to |
||
List<LinkedFile> changedFiles = new ArrayList<>(); | ||
List<String> exceptions = new ArrayList<>(); | ||
|
||
for (LinkedFile file : listlinked) { | ||
Path path = Path.of(file.getLink()); | ||
if (!Files.exists(path)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is incomplete. Use Reason: JabRef has multiple directories to store files. See https://docs.jabref.org/finding-sorting-and-cleaning-entries/filelinks#directories-for-files for an explanation. |
||
Path filePath = Path.of(file.getLink()); | ||
// May need to put some limit since it can cause considerable performance problems. | ||
String directoryPath = filePath.getParent().getParent().toString(); | ||
|
||
String fileNameString = filePath.getFileName().toString(); | ||
|
||
List<Path> fileLocations = searchFileInDirectoryAndSubdirectories(Path.of(directoryPath), fileNameString); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong start. Search in |
||
if (!fileLocations.isEmpty()) { | ||
// File locations is a list but as stated in the link below, it is a rare case. But can be solved if time allows. | ||
// https://github.com/JabRef/jabref/issues/9798#issuecomment-1524155132 | ||
file.setLink(fileLocations.get(0).toString()); | ||
changedFiles.add(file); | ||
} else { | ||
exceptions.add(fileNameString); | ||
throw new FileNotFoundException(); | ||
Comment on lines
+174
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No exception here. Just leave the link as is. Exceptions are huge derivations from the normal results. See, if I have a library wtih 10000 PDFs, moved 20 files and 19 can be relinked, the whole processing should not be stopped if one file cannot be found. It is OK to report a list of files which could not be relinked. If this is hard, then just log the non-found files. |
||
} | ||
} | ||
} | ||
return new RelinkedResults(changedFiles, exceptions); | ||
} | ||
|
||
public List<Path> searchFileInDirectoryAndSubdirectories(Path directory, String targetFileName) { | ||
List<Path> paths = new ArrayList<>(); | ||
try { | ||
Files.walk(directory, Integer.MAX_VALUE, FileVisitOption.FOLLOW_LINKS) | ||
.filter(path -> Files.isRegularFile(path)) | ||
.filter(path -> path.getFileName().toString().equals(targetFileName)) | ||
.forEach(paths::add); | ||
} catch (IOException e) { | ||
// Handle any exceptions here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be ChatGPT generated. You can simply log the exception. |
||
} | ||
List<Path> output = new ArrayList<>(); | ||
for (Path p : paths) { | ||
output.add(p); | ||
} | ||
return output; | ||
Comment on lines
+192
to
+196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this? Why not |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
|
||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.StandardCopyOption; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.TreeSet; | ||
|
@@ -26,7 +28,6 @@ | |
import static org.mockito.Mockito.when; | ||
|
||
public class AutoSetFileLinksUtilTest { | ||
|
||
private final FilePreferences filePreferences = mock(FilePreferences.class); | ||
private final AutoLinkPreferences autoLinkPrefs = new AutoLinkPreferences( | ||
AutoLinkPreferences.CitationKeyDependency.START, | ||
|
@@ -47,7 +48,7 @@ public void setUp(@TempDir Path folder) throws Exception { | |
} | ||
|
||
@Test | ||
public void findAssociatedNotLinkedFilesSuccess() throws Exception { | ||
public void testFindAssociatedNotLinkedFilesSuccess() throws Exception { | ||
when(databaseContext.getFileDirectories(any())).thenReturn(Collections.singletonList(path.getParent())); | ||
List<LinkedFile> expected = Collections.singletonList(new LinkedFile("", Path.of("CiteKey.pdf"), "PDF")); | ||
AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); | ||
|
@@ -56,10 +57,78 @@ public void findAssociatedNotLinkedFilesSuccess() throws Exception { | |
} | ||
|
||
@Test | ||
public void findAssociatedNotLinkedFilesForEmptySearchDir() throws Exception { | ||
public void testFindAssociatedNotLinkedFilesForEmptySearchDir() throws Exception { | ||
when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(false); | ||
AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); | ||
List<LinkedFile> actual = util.findAssociatedNotLinkedFiles(entry); | ||
assertEquals(Collections.emptyList(), actual); | ||
} | ||
|
||
/* @BeforeEach | ||
public void setUp(@TempDir Path folder) throws Exception { | ||
A = folder.resolve("A"); | ||
Files.createDirectory(A); | ||
B = folder.resolve("B"); | ||
Files.createDirectory(B); | ||
path = A.resolve("CiteKey.pdf"); | ||
Files.createFile(path); | ||
entry.setCitationKey("CiteKey"); | ||
when(filePreferences.getExternalFileTypes()) | ||
.thenReturn(FXCollections.observableSet(new TreeSet<>(ExternalFileTypes.getDefaultExternalFileTypes()))); | ||
} */ | ||
|
||
@Test | ||
public void testFileLinksAfterMoving() throws Exception { | ||
// Run "Automatically set file links" - check that the bib file was not modified | ||
path = path.getParent(); | ||
|
||
Path A = path.resolve("A"); | ||
Files.createDirectory(A); | ||
Path B = path.resolve("B"); | ||
Files.createDirectory(B); | ||
Path filePath = A.resolve("Test.pdf"); | ||
Files.createFile(filePath); | ||
entry.setCitationKey("Test"); | ||
|
||
LinkedFile linkedFile = new LinkedFile("desc", filePath, "PDF"); | ||
List<LinkedFile> listLinked = new ArrayList<>(); | ||
listLinked.add(linkedFile); | ||
entry.setFiles(listLinked); | ||
|
||
Path destination = B.resolve("Test.pdf"); | ||
Files.move(A.resolve("Test.pdf"), destination, StandardCopyOption.REPLACE_EXISTING); | ||
|
||
AutoSetFileLinksUtil util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); | ||
AutoSetFileLinksUtil.RelinkedResults results = util.relinkingFiles(entry.getFiles()); | ||
List<LinkedFile> list = results.relinkedFiles(); | ||
List<String> exceptions = results.exceptions(); | ||
assertEquals(Collections.emptyList(), exceptions); | ||
|
||
// Change Entry to match required result and run method on bib | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "required result" is too unspecifiy. Explain, what is the intention. |
||
LinkedFile linkedDestFile = new LinkedFile("desc", B.resolve("Test.pdf"), "PDF"); | ||
List<LinkedFile> listLinked2 = new ArrayList<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having numbers as variable name suffix is bad style. Please craft self-descriptive variable names. E.g., "existingFiles" or something. |
||
listLinked2.add(linkedDestFile); | ||
entry.setFiles(listLinked2); | ||
assertEquals(entry.getFiles(), list); | ||
|
||
// After this point, tested different extension along with the case where document has multiple file references | ||
Path filePath2 = B.resolve("Test1.txt"); | ||
Files.createFile(filePath2); | ||
linkedFile = new LinkedFile("desc", filePath2, "TXT"); | ||
listLinked.add(linkedFile); | ||
entry.setFiles(listLinked); | ||
|
||
destination = A.resolve("Test1.txt"); | ||
Files.move(B.resolve("Test1.txt"), destination, StandardCopyOption.REPLACE_EXISTING); | ||
util = new AutoSetFileLinksUtil(databaseContext, filePreferences, autoLinkPrefs); | ||
results = util.relinkingFiles(entry.getFiles()); | ||
list = results.relinkedFiles(); | ||
exceptions = results.exceptions(); | ||
|
||
listLinked2.clear(); | ||
listLinked2.add(new LinkedFile("desc", B.resolve("Test.pdf"), "PDF")); | ||
listLinked2.add(new LinkedFile("desc", A.resolve("Test1.txt"), "TXT")); | ||
|
||
assertEquals(listLinked2, list); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CHANGELOG is only for user-facing changes. You can just remove the last part.
Please add a full link to the issue as shown in the other lines of the CHANGELOG.md