From 6b73b3fc013d81f36afe2aca136602dec0483273 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:00:29 +0200 Subject: [PATCH 01/24] refactor(RightClickMenu): pass libraryTab to createCopySubMenu Allows tracking the source BibDatabaseContext for later use in pasteEntry() --- .../main/java/org/jabref/gui/maintable/RightClickMenu.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index ba4d8ab2119..3780cd313df 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -73,7 +73,7 @@ public static ContextMenu create(BibEntryTableViewModel entry, contextMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager)), - createCopySubMenu(factory, dialogService, stateManager, preferences, clipBoardManager, abbreviationRepository, taskExecutor), + createCopySubMenu(factory, dialogService, stateManager, preferences, libraryTab, clipBoardManager, abbreviationRepository, taskExecutor), createCopyToMenu(factory, dialogService, stateManager, preferences, libraryTab, importHandler), factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, () -> libraryTab, stateManager, undoManager)), factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, () -> libraryTab, stateManager, undoManager)), @@ -171,11 +171,14 @@ private static Menu createCopySubMenu(ActionFactory factory, DialogService dialogService, StateManager stateManager, GuiPreferences preferences, + LibraryTab libraryTab, ClipBoardManager clipBoardManager, JournalAbbreviationRepository abbreviationRepository, TaskExecutor taskExecutor) { Menu copySpecialMenu = factory.createMenu(StandardActions.COPY_MORE); + clipBoardManager.set + copySpecialMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), factory.createMenuItem(StandardActions.COPY_KEY, new CopyMoreAction(StandardActions.COPY_KEY, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), From f036db2eb2f55ce6a91f9d320b437f7cf345d656 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:02:04 +0200 Subject: [PATCH 02/24] feat(ClipBoardManager): add static BibDatabaseContext field with getter and setter Enables tracking the source database context for use in pasteEntry() --- .../main/java/org/jabref/gui/ClipBoardManager.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java index d9c8617b526..b602d026b02 100644 --- a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -7,6 +7,7 @@ import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; import java.util.List; +import java.util.Optional; import javafx.application.Platform; import javafx.scene.control.TextInputControl; @@ -19,6 +20,7 @@ import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; @@ -35,6 +37,8 @@ public class ClipBoardManager { private static final Logger LOGGER = LoggerFactory.getLogger(ClipBoardManager.class); + private static BibDatabaseContext sourceDatabaseContext; + private static Clipboard clipboard; private static java.awt.datatransfer.Clipboard primary; @@ -117,6 +121,10 @@ public static String getContentsPrimary() { return getContents(); } + public static Optional getSourceBibDatabaseContext() { + return Optional.ofNullable(sourceDatabaseContext); + } + /** * Puts content onto the system clipboard. * @@ -166,6 +174,12 @@ public void setContent(List entries, BibEntryTypesManager entryTypesMa setContent(builder.toString()); } + public static void setSourceBibDatabaseContext(BibDatabaseContext context) { + if (context != null) { + sourceDatabaseContext = context; + } + } + private String serializeEntries(List entries, BibEntryTypesManager entryTypesManager) throws IOException { CliPreferences preferences = Injector.instantiateModelOrService(CliPreferences.class); // BibEntry is not Java serializable. Thus, we need to do the serialization manually From ddb7fffb47036d900c97d1dbef755b8dcdbf4874 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:10:31 +0200 Subject: [PATCH 03/24] refactor(ClipBoardManager): change sourceDatabaseContext to instance variable --- jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java index b602d026b02..a0eb25f4642 100644 --- a/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java +++ b/jabgui/src/main/java/org/jabref/gui/ClipBoardManager.java @@ -37,7 +37,7 @@ public class ClipBoardManager { private static final Logger LOGGER = LoggerFactory.getLogger(ClipBoardManager.class); - private static BibDatabaseContext sourceDatabaseContext; + private BibDatabaseContext sourceDatabaseContext; private static Clipboard clipboard; private static java.awt.datatransfer.Clipboard primary; @@ -121,7 +121,7 @@ public static String getContentsPrimary() { return getContents(); } - public static Optional getSourceBibDatabaseContext() { + public Optional getSourceBibDatabaseContext() { return Optional.ofNullable(sourceDatabaseContext); } @@ -174,7 +174,7 @@ public void setContent(List entries, BibEntryTypesManager entryTypesMa setContent(builder.toString()); } - public static void setSourceBibDatabaseContext(BibDatabaseContext context) { + public void setSourceBibDatabaseContext(BibDatabaseContext context) { if (context != null) { sourceDatabaseContext = context; } From 9da934b624d69ec835f6c77cac9ece9be9b3fe79 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:12:09 +0200 Subject: [PATCH 04/24] feat(RightClickMenu): set sourceBibDatabaseContext from libraryTab --- .../src/main/java/org/jabref/gui/maintable/RightClickMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 3780cd313df..30c48fc27c4 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -177,7 +177,7 @@ private static Menu createCopySubMenu(ActionFactory factory, TaskExecutor taskExecutor) { Menu copySpecialMenu = factory.createMenu(StandardActions.COPY_MORE); - clipBoardManager.set + clipBoardManager.setSourceBibDatabaseContext(libraryTab.getBibDatabaseContext()); copySpecialMenu.getItems().addAll( factory.createMenuItem(StandardActions.COPY_TITLE, new CopyMoreAction(StandardActions.COPY_TITLE, dialogService, stateManager, clipBoardManager, preferences, abbreviationRepository)), From 83070d5d21ff7330c7fe242af54ad6c895180ed1 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:25:31 +0200 Subject: [PATCH 05/24] feat: add LinkedFileTransferHelper#adjustLinkedFilesForTarget call to CopyTo#copyEntriesWithFeedback and LibraryTab#pasteEntry --- jabgui/src/main/java/org/jabref/gui/LibraryTab.java | 6 ++++++ jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index d7223197d16..79dc472bafa 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -60,6 +60,7 @@ import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.ai.AiService; import org.jabref.logic.citationstyle.CitationStyleCache; +import org.jabref.logic.externalfiles.LinkedFileTransferHelper; import org.jabref.logic.importer.FetcherClientException; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.FetcherServerException; @@ -827,6 +828,11 @@ public void pasteEntry() { return; } + if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { + LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, + clipBoardManager.getSourceBibDatabaseContext().get(), bibDatabaseContext); + } + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); } diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java index 2164994660c..d596d357ab5 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java @@ -10,6 +10,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.externalfiles.EntryImportHandlerTracker; import org.jabref.gui.externalfiles.ImportHandler; +import org.jabref.logic.externalfiles.LinkedFileTransferHelper; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -105,6 +106,8 @@ private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseCon } }); + LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, sourceDatabaseContext, targetDatabaseContext); + importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, entriesToAdd, tracker); } From 36cc6af343f79c89fb55c5c21cb86412dd5d573c Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:54:52 +0200 Subject: [PATCH 06/24] feat(CopyTo): support file preferences in copy operations Add filePreferences parameter to CopyTo.java to enable LinkedFile#findIn functionality --- .../main/java/org/jabref/gui/edit/CopyTo.java | 7 ++++++- .../jabref/gui/maintable/RightClickMenu.java | 3 ++- .../java/org/jabref/gui/edit/CopyToTest.java | 17 +++++++++++------ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java index d596d357ab5..208f5c7e7a9 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java @@ -10,6 +10,7 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.externalfiles.EntryImportHandlerTracker; import org.jabref.gui.externalfiles.ImportHandler; +import org.jabref.logic.FilePreferences; import org.jabref.logic.externalfiles.LinkedFileTransferHelper; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -26,6 +27,7 @@ public class CopyTo extends SimpleCommand { private final DialogService dialogService; private final StateManager stateManager; private final CopyToPreferences copyToPreferences; + private final FilePreferences filePreferences; private final ImportHandler importHandler; private final BibDatabaseContext sourceDatabaseContext; private final BibDatabaseContext targetDatabaseContext; @@ -33,12 +35,14 @@ public class CopyTo extends SimpleCommand { public CopyTo(DialogService dialogService, StateManager stateManager, CopyToPreferences copyToPreferences, + FilePreferences filePreferences, ImportHandler importHandler, BibDatabaseContext sourceDatabaseContext, BibDatabaseContext targetDatabaseContext) { this.dialogService = dialogService; this.stateManager = stateManager; this.copyToPreferences = copyToPreferences; + this.filePreferences = filePreferences; this.importHandler = importHandler; this.sourceDatabaseContext = sourceDatabaseContext; this.targetDatabaseContext = targetDatabaseContext; @@ -106,7 +110,8 @@ private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseCon } }); - LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, sourceDatabaseContext, targetDatabaseContext); + LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, sourceDatabaseContext, targetDatabaseContext, + filePreferences); importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, entriesToAdd, tracker); } diff --git a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 30c48fc27c4..6c7c3e25d7c 100644 --- a/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/jabgui/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -157,7 +157,8 @@ private static Menu createCopyToMenu(ActionFactory factory, copyToMenu.getItems().addAll( factory.createCustomMenuItem( StandardActions.COPY_TO, - new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, bibDatabaseContext), + new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, bibDatabaseContext), destinationDatabaseName ) ); diff --git a/jabgui/src/test/java/org/jabref/gui/edit/CopyToTest.java b/jabgui/src/test/java/org/jabref/gui/edit/CopyToTest.java index 876791c1afb..183bd0e0a9a 100644 --- a/jabgui/src/test/java/org/jabref/gui/edit/CopyToTest.java +++ b/jabgui/src/test/java/org/jabref/gui/edit/CopyToTest.java @@ -84,7 +84,7 @@ void executeCopyEntriesWithoutCrossRef() { selectedEntries.add(entry); when(stateManager.getSelectedEntries()).thenReturn((ObservableList) selectedEntries); - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); ArgumentCaptor trackerCaptor = ArgumentCaptor.forClass(EntryImportHandlerTracker.class); @@ -98,7 +98,8 @@ void executeCopyEntriesWithCrossRef() { selectedEntries.add(entryWithCrossRef); when(stateManager.getSelectedEntries()).thenReturn((ObservableList) selectedEntries); - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); ArgumentCaptor trackerCaptor = ArgumentCaptor.forClass(EntryImportHandlerTracker.class); copyTo.copyEntriesWithCrossRef(selectedEntries, targetDatabaseContext); @@ -111,7 +112,8 @@ void executeCopyEntriesWithCrossRef() { @Test void executeGetCrossRefEntry() { - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); BibEntry result = copyTo.getCrossRefEntry(entryWithCrossRef, sourceDatabaseContext).orElse(null); @@ -125,7 +127,8 @@ void executeExecuteWithoutCrossRef() { when(stateManager.getSelectedEntries()).thenReturn((ObservableList) selectedEntries); when(preferences.getCopyToPreferences().getShouldAskForIncludingCrossReferences()).thenReturn(false); - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); ArgumentCaptor trackerCaptor = ArgumentCaptor.forClass(EntryImportHandlerTracker.class); copyTo.execute(); @@ -140,7 +143,8 @@ void executeExecuteWithCrossRefAndUserIncludes() { when(preferences.getCopyToPreferences().getShouldAskForIncludingCrossReferences()).thenReturn(true); when(dialogService.showConfirmationDialogWithOptOutAndWait(anyString(), anyString(), anyString(), anyString(), anyString(), any())).thenReturn(true); - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); ArgumentCaptor trackerCaptor = ArgumentCaptor.forClass(EntryImportHandlerTracker.class); copyTo.execute(); @@ -158,7 +162,8 @@ void executeWithCrossRefAndUserExcludes() { when(preferences.getCopyToPreferences().getShouldAskForIncludingCrossReferences()).thenReturn(true); when(dialogService.showConfirmationDialogWithOptOutAndWait(anyString(), anyString(), anyString(), anyString(), anyString(), any())).thenReturn(false); - copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); + copyTo = new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), + preferences.getFilePreferences(), importHandler, sourceDatabaseContext, targetDatabaseContext); ArgumentCaptor trackerCaptor = ArgumentCaptor.forClass(EntryImportHandlerTracker.class); copyTo.execute(); From fd355cf78853ab6fa46b3dba12e94878c343c5fe Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 00:57:09 +0200 Subject: [PATCH 07/24] feat(LinkedFileTransferHelper): add new helper class to support Entry transfer --- .../main/java/org/jabref/gui/LibraryTab.java | 2 +- .../LinkedFileTransferHelper.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index 79dc472bafa..e64b1d6eb27 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -830,7 +830,7 @@ public void pasteEntry() { if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, - clipBoardManager.getSourceBibDatabaseContext().get(), bibDatabaseContext); + clipBoardManager.getSourceBibDatabaseContext().get(), bibDatabaseContext, preferences.getFilePreferences()); } importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java new file mode 100644 index 00000000000..b0f7a53c13e --- /dev/null +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -0,0 +1,19 @@ +package org.jabref.logic.externalfiles; + +import org.jabref.logic.FilePreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; + +import java.util.List; + +public class LinkedFileTransferHelper { + + public static void adjustLinkedFilesForTarget( + List entries, + BibDatabaseContext sourceContext, + BibDatabaseContext targetContext, + FilePreferences filePreferences + ) { + + } +} From fb76057241a6efb979f685c2c381344ae8408c62 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 08:36:09 +0200 Subject: [PATCH 08/24] feat(LinkedFileTransferHelper): add helper to check reachability from primary directory --- .../LinkedFileTransferHelper.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index b0f7a53c13e..c761173f987 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -4,6 +4,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import java.nio.file.Path; import java.util.List; public class LinkedFileTransferHelper { @@ -14,6 +15,32 @@ public static void adjustLinkedFilesForTarget( BibDatabaseContext targetContext, FilePreferences filePreferences ) { + for (BibEntry entry : entries) { + entry.getFiles().forEach(linkedFile -> { + Path targetFile = linkedFile.findIn(sourceContext.getFileDirectories(filePreferences)).orElse(null); + if (targetFile != null && isReachableFromPrimaryDirectory(targetFile, targetContext, filePreferences)) { + System.out.println("File is reachable from target context: " + targetFile); + linkedFile.setLink(targetFile.toString()); + } else { + System.out.println("File is NOT reachable from target context: " + targetFile); + } + }); + } + } + + public static boolean isReachableFromPrimaryDirectory(Path resolvedFile, BibDatabaseContext targetContext, FilePreferences filePreferences) { + List directories = targetContext.getFileDirectories(filePreferences); + if (directories.isEmpty()) { + return false; + } + + Path primary = directories.getFirst(); + try { + Path relative = primary.relativize(resolvedFile); + return !relative.startsWith("..") && !relative.isAbsolute(); + } catch (IllegalArgumentException e) { + return false; + } } } From a5a48a7db4e96f832359069c7df125b427ed5e6a Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 09:02:21 +0200 Subject: [PATCH 09/24] test(LinkedFileTransferHelperTest): add test cases for LinkedFileTransferHelperTest#isReachableFromPrimaryDirectory method --- .../LinkedFileTransferHelperTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java new file mode 100644 index 00000000000..77976222700 --- /dev/null +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -0,0 +1,53 @@ +package org.jabref.logic.externalfiles; + +import org.jabref.logic.FilePreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class LinkedFileTransferHelperTest { + + @Nested + class IsReachableFromPrimaryDirectoryTest { + + FilePreferences prefs = mock(FilePreferences.class); + BibDatabaseContext targetContext = mock(BibDatabaseContext.class); + + @Test + void case1_reachableWithinPrimaryDirectory() { + Path targetPrimaryDir = Path.of("/project"); + Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + + when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + + assertTrue(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + } + + @Test + void case2_sameRelativePathButDifferentBaseDirectory_shouldNotBeReachable() { + Path targetPrimaryDir = Path.of("/project/parentfiles"); + Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + + when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + + assertFalse(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + } + + @Test + void case3_differentRelativePathAndDifferentBaseDirectory_shouldNotBeReachable() { + Path targetPrimaryDir = Path.of("/project/parentfiles"); + Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + + when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + + assertFalse(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + } + } +} From 427c12c9cc957dcab131efe13e78ed59304a61b0 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 7 Jul 2025 19:35:42 +0200 Subject: [PATCH 10/24] feat: adjust relative file paths after entry transfer between libraries --- .../main/java/org/jabref/gui/LibraryTab.java | 9 +- .../main/java/org/jabref/gui/edit/CopyTo.java | 6 +- jablib/src/main/java/module-info.java | 11 +-- .../LinkedFileTransferHelper.java | 90 ++++++++++++++----- 4 files changed, 82 insertions(+), 34 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index e64b1d6eb27..a5ea6244cf6 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -43,6 +43,7 @@ import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.exporter.SaveDatabaseAction; +import org.jabref.gui.externalfiles.EntryImportHandlerTracker; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.fieldeditors.LinkedFileViewModel; import org.jabref.gui.importer.actions.OpenDatabaseAction; @@ -828,12 +829,12 @@ public void pasteEntry() { return; } - if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { - LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, - clipBoardManager.getSourceBibDatabaseContext().get(), bibDatabaseContext, preferences.getFilePreferences()); - } + EntryImportHandlerTracker tracker = new EntryImportHandlerTracker(entriesToAdd.size()); importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); + tracker.setOnFinish(() -> LinkedFileTransferHelper + .adjustLinkedFilesForTarget(bibDatabaseContext, preferences.getFilePreferences()) + ); } private List handleNonBibTeXStringData(String data) { diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java index 208f5c7e7a9..bc502313f83 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java @@ -110,10 +110,10 @@ private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseCon } }); - LinkedFileTransferHelper.adjustLinkedFilesForTarget(entriesToAdd, sourceDatabaseContext, targetDatabaseContext, - filePreferences); - importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, entriesToAdd, tracker); + tracker.setOnFinish(() -> LinkedFileTransferHelper + .adjustLinkedFilesForTarget(targetDatabaseContext, filePreferences) + ); } public Optional getCrossRefEntry(BibEntry bibEntryToCheck, BibDatabaseContext sourceDatabaseContext) { diff --git a/jablib/src/main/java/module-info.java b/jablib/src/main/java/module-info.java index 07bd3b6884e..8d9a0bc0a46 100644 --- a/jablib/src/main/java/module-info.java +++ b/jablib/src/main/java/module-info.java @@ -107,17 +107,14 @@ exports org.jabref.logic.pseudonymization; exports org.jabref.logic.citation.repository; - requires java.base; - - requires javafx.base; + requires javafx.base; requires javafx.graphics; // because of javafx.scene.paint.Color requires afterburner.fx; requires com.tobiasdiez.easybind; // for java.awt.geom.Rectangle2D required by org.jabref.logic.pdf.TextExtractor - requires java.desktop; - // SQL + // SQL requires java.sql; requires java.sql.rowset; @@ -249,6 +246,6 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; - requires org.jetbrains.annotations; - // endregion + requires org.jetbrains.annotations; + // endregion } diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index c761173f987..3a6d680eeaf 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -3,44 +3,94 @@ import org.jabref.logic.FilePreferences; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; public class LinkedFileTransferHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileTransferHelper.class); + public static void adjustLinkedFilesForTarget( - List entries, - BibDatabaseContext sourceContext, BibDatabaseContext targetContext, FilePreferences filePreferences ) { - for (BibEntry entry : entries) { - entry.getFiles().forEach(linkedFile -> { - Path targetFile = linkedFile.findIn(sourceContext.getFileDirectories(filePreferences)).orElse(null); - if (targetFile != null && isReachableFromPrimaryDirectory(targetFile, targetContext, filePreferences)) { - System.out.println("File is reachable from target context: " + targetFile); - linkedFile.setLink(targetFile.toString()); - } else { - System.out.println("File is NOT reachable from target context: " + targetFile); + for (BibEntry entry : targetContext.getEntries()) { + boolean entryChanged = false; + List linkedFiles = new ArrayList<>(); + for (LinkedFile linkedFile : entry.getFiles()) { + if (linkedFile == null || linkedFile.getLink().isEmpty()) { + continue; + } + + Optional currentOpt = linkedFile.findIn(targetContext, filePreferences); + if (currentOpt.isEmpty()) { + continue; + } + + Path current = currentOpt.get(); + Optional primaryOpt = getPrimaryPath(targetContext, filePreferences); + if (primaryOpt.isEmpty()) { + LOGGER.warn("No primary directory found for current context, cannot adjust linked file: {}", linkedFile); + linkedFiles.add(linkedFile); + continue; + } + + Path primary = primaryOpt.get(); + + try { + Path relative = primary.relativize(current); + + String currentLink = linkedFile.getLink(); + String newLink = relative.toString(); + + boolean needsPathUpdate = !currentLink.equals(newLink); + boolean reachable = isReachableFromPrimaryDirectory(relative); + + if (!reachable) { + copyFile(); + } + + if (needsPathUpdate && reachable) { + linkedFile.setLink(newLink); + entryChanged = true; + } + linkedFiles.add(linkedFile); + } catch (IllegalArgumentException e) { + LOGGER.warn("Cannot relativize path {} against primary directory {}: {}", + current, primary, e.getMessage()); + linkedFiles.add(linkedFile); } - }); + } + if (entryChanged) { + entry.setFiles(linkedFiles); + } } } - public static boolean isReachableFromPrimaryDirectory(Path resolvedFile, BibDatabaseContext targetContext, FilePreferences filePreferences) { - List directories = targetContext.getFileDirectories(filePreferences); - if (directories.isEmpty()) { - return false; - } - - Path primary = directories.getFirst(); + private static void copyFile() { + throw new UnsupportedOperationException("Copying files is not implemented yet."); + } + public static boolean isReachableFromPrimaryDirectory(Path relativePath) { try { - Path relative = primary.relativize(resolvedFile); - return !relative.startsWith("..") && !relative.isAbsolute(); + return !relativePath.startsWith("..") && !relativePath.isAbsolute(); } catch (IllegalArgumentException e) { return false; } } + + private static Optional getPrimaryPath(BibDatabaseContext targetContext, FilePreferences filePreferences) { + List directories = targetContext.getFileDirectories(filePreferences); + if (directories.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(directories.getFirst()); + } } From 8b529721d2d808fa3cc575d19a3021f9f012b842 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sat, 12 Jul 2025 09:23:53 +0200 Subject: [PATCH 11/24] feat(ImportHandler): clone entry in importEntryWithDuplicateCheck Cloning the BibEntry in importEntryWithDuplicateCheck to ensure that any file path adjustments during the copy/paste process do not affect the original entry in the source database. This avoids side effects when imported entries are modified (e.g. file links), especially when the same BibEntry instance is shared between databases. --- .../main/java/org/jabref/gui/externalfiles/ImportHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index aa7ef91c847..778a8dd5a52 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -251,7 +251,7 @@ public void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, } private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry, DuplicateResolverDialog.DuplicateResolverResult decision, EntryImportHandlerTracker tracker) { - BibEntry entryToInsert = cleanUpEntry(bibDatabaseContext, entry); + BibEntry entryToInsert = (BibEntry) cleanUpEntry(bibDatabaseContext, entry).clone(); BackgroundTask.wrap(() -> findDuplicate(bibDatabaseContext, entryToInsert)) .onFailure(e -> { From 83ad967b6354241eeedaea0e66238483146941cc Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sat, 12 Jul 2025 09:43:27 +0200 Subject: [PATCH 12/24] chore: update LinkedFileTransferHelper#adjustLinkedFilesForTarget calls in LibraryTab and CopyTo to align with new method signature --- jabgui/src/main/java/org/jabref/gui/LibraryTab.java | 8 +++++--- jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index a5ea6244cf6..a44fd1ff622 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -832,9 +832,11 @@ public void pasteEntry() { EntryImportHandlerTracker tracker = new EntryImportHandlerTracker(entriesToAdd.size()); importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); - tracker.setOnFinish(() -> LinkedFileTransferHelper - .adjustLinkedFilesForTarget(bibDatabaseContext, preferences.getFilePreferences()) - ); + if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { + tracker.setOnFinish(() -> LinkedFileTransferHelper + .adjustLinkedFilesForTarget(clipBoardManager.getSourceBibDatabaseContext().get(), + bibDatabaseContext, preferences.getFilePreferences())); + } } private List handleNonBibTeXStringData(String data) { diff --git a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java index bc502313f83..4603b5611a1 100644 --- a/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java +++ b/jabgui/src/main/java/org/jabref/gui/edit/CopyTo.java @@ -112,7 +112,7 @@ private void copyEntriesWithFeedback(List entriesToAdd, BibDatabaseCon importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, entriesToAdd, tracker); tracker.setOnFinish(() -> LinkedFileTransferHelper - .adjustLinkedFilesForTarget(targetDatabaseContext, filePreferences) + .adjustLinkedFilesForTarget(sourceDatabaseContext, targetDatabaseContext, filePreferences) ); } From 35e97bcbc38b16b5b77f52cbac0690fba1d701d9 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sat, 12 Jul 2025 10:12:45 +0200 Subject: [PATCH 13/24] refactor: update LibraryTab#pasteEntry to copy with feedback --- .../main/java/org/jabref/gui/LibraryTab.java | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index a44fd1ff622..788f72206c8 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -818,28 +818,55 @@ private int doCopyEntry(List selectedEntries) { } } - public void pasteEntry() { - List entriesToAdd; - String content = ClipBoardManager.getContents(); - entriesToAdd = importHandler.handleBibTeXData(content); - if (entriesToAdd.isEmpty()) { - entriesToAdd = handleNonBibTeXStringData(content); - } - if (entriesToAdd.isEmpty()) { - return; - } + public void pasteEntry() { + String content = ClipBoardManager.getContents(); + List entriesToAdd = importHandler.handleBibTeXData(content); + if (entriesToAdd.isEmpty()) { + entriesToAdd = handleNonBibTeXStringData(content); + } + if (entriesToAdd.isEmpty()) { + return; + } + copyEntriesWithFeedback(entriesToAdd); + } - EntryImportHandlerTracker tracker = new EntryImportHandlerTracker(entriesToAdd.size()); + private void copyEntriesWithFeedback(List entriesToAdd) { + final List finalEntriesToAdd = entriesToAdd; - importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, entriesToAdd); - if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { - tracker.setOnFinish(() -> LinkedFileTransferHelper - .adjustLinkedFilesForTarget(clipBoardManager.getSourceBibDatabaseContext().get(), - bibDatabaseContext, preferences.getFilePreferences())); - } + EntryImportHandlerTracker tracker = new EntryImportHandlerTracker(finalEntriesToAdd.size()); + + tracker.setOnFinish(() -> { + int importedCount = tracker.getImportedCount(); + int skippedCount = tracker.getSkippedCount(); + + String targetName = bibDatabaseContext.getDatabasePath() + .map(path -> path.getFileName().toString()) + .orElse(Localization.lang("target library")); + + if (importedCount == finalEntriesToAdd.size()) { + dialogService.notify(Localization.lang("Pasted %0 entry(s) to %1", + String.valueOf(importedCount), targetName)); + } else if (importedCount == 0) { + dialogService.notify(Localization.lang("No entry was pasted to %0", targetName)); + } else { + dialogService.notify(Localization.lang("Pasted %0 entry(s) to %1. %2 were skipped", + String.valueOf(importedCount), targetName, String.valueOf(skippedCount))); + } + }); + + // Import mit Duplikat-Check durchführen + importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, finalEntriesToAdd, tracker); + + // Linked Files anpassen falls Source-Context vorhanden + if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { + tracker.setOnFinish(() -> LinkedFileTransferHelper + .adjustLinkedFilesForTarget(clipBoardManager.getSourceBibDatabaseContext().get(), + bibDatabaseContext, preferences.getFilePreferences())); } + } + - private List handleNonBibTeXStringData(String data) { + private List handleNonBibTeXStringData(String data) { try { return this.importHandler.handleStringData(data); } catch (FetcherException exception) { From bf4b442b4bc3ac707eb35aa67766097860762fda Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sat, 12 Jul 2025 12:13:51 +0200 Subject: [PATCH 14/24] feat(LinkedFileTransferHelper): enhance linked file adjustment and copying logic during entry transfer --- .../LinkedFileTransferHelper.java | 224 +++++++++++++----- 1 file changed, 168 insertions(+), 56 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index 3a6d680eeaf..90ac8b1b434 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -1,82 +1,115 @@ package org.jabref.logic.externalfiles; import org.jabref.logic.FilePreferences; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; public class LinkedFileTransferHelper { + private static record FileCopyContext( + BibDatabaseContext sourceContext, + BibDatabaseContext targetContext, + FilePreferences filePreferences + ) {} + private static final Logger LOGGER = LoggerFactory.getLogger(LinkedFileTransferHelper.class); - public static void adjustLinkedFilesForTarget( + /** + * Adjusts linked files when copying entries from source to target context. + * Files that are not reachable from the target context will be copied. + * Files in the target context whose relative paths differ from the source will have their paths adjusted + * + * @param sourceContext The source database context where files are currently located + * @param targetContext The target database context where files should be accessible + * @param filePreferences File preferences for both contexts + */ + public static Set adjustLinkedFilesForTarget( + BibDatabaseContext sourceContext, BibDatabaseContext targetContext, FilePreferences filePreferences ) { - for (BibEntry entry : targetContext.getEntries()) { - boolean entryChanged = false; - List linkedFiles = new ArrayList<>(); - for (LinkedFile linkedFile : entry.getFiles()) { - if (linkedFile == null || linkedFile.getLink().isEmpty()) { - continue; - } - - Optional currentOpt = linkedFile.findIn(targetContext, filePreferences); - if (currentOpt.isEmpty()) { - continue; - } - - Path current = currentOpt.get(); - Optional primaryOpt = getPrimaryPath(targetContext, filePreferences); - if (primaryOpt.isEmpty()) { - LOGGER.warn("No primary directory found for current context, cannot adjust linked file: {}", linkedFile); - linkedFiles.add(linkedFile); - continue; - } - - Path primary = primaryOpt.get(); - - try { - Path relative = primary.relativize(current); - - String currentLink = linkedFile.getLink(); - String newLink = relative.toString(); - - boolean needsPathUpdate = !currentLink.equals(newLink); - boolean reachable = isReachableFromPrimaryDirectory(relative); - - if (!reachable) { - copyFile(); - } - - if (needsPathUpdate && reachable) { - linkedFile.setLink(newLink); - entryChanged = true; - } - linkedFiles.add(linkedFile); - } catch (IllegalArgumentException e) { - LOGGER.warn("Cannot relativize path {} against primary directory {}: {}", - current, primary, e.getMessage()); - linkedFiles.add(linkedFile); - } + + Set modifiedEntries = new HashSet<>(); + + FileCopyContext context = new FileCopyContext(sourceContext, targetContext, filePreferences); + + for (BibEntry entry : targetContext.getEntries()) { + boolean entryChanged = false; + List linkedFiles = new ArrayList<>(); + + for (LinkedFile linkedFile : entry.getFiles()) { + if (linkedFile == null || linkedFile.getLink().isEmpty()) { + linkedFiles.add(linkedFile); + continue; + } + + Optional sourcePathOpt = linkedFile.findIn(context.sourceContext(), context.filePreferences()); + Optional targetPrimaryOpt = getPrimaryPath(context.targetContext(), context.filePreferences()); + + if (sourcePathOpt.isEmpty() || targetPrimaryOpt.isEmpty()) { + linkedFiles.add(linkedFile); + continue; + } + + Path relative; + if (sourcePathOpt.get().startsWith(targetPrimaryOpt.get())) { + relative = targetPrimaryOpt.get().relativize(sourcePathOpt.get()); + } else { + // Fallback: mark as unreachable, so the file will be copied + relative = Path.of("..").resolve(sourcePathOpt.get().getFileName()); } - if (entryChanged) { - entry.setFiles(linkedFiles); + + if (isReachableFromPrimaryDirectory(relative)) { + entryChanged = isPathAdjusted(linkedFile, relative, linkedFiles, entryChanged); + } else { + entryChanged = isFileCopied(context, linkedFile, entryChanged, linkedFiles); } } + if (entryChanged) { + entry.setFiles(linkedFiles); + modifiedEntries.add(entry); + } + } + return modifiedEntries; } - private static void copyFile() { - throw new UnsupportedOperationException("Copying files is not implemented yet."); + /** + * Gets the primary directory path for the given context. + * This is a utility method extracted from the original implementation. + * + * @param context The database context + * @param filePreferences File preferences for the context + * @return Optional containing the primary directory path, or empty if none found + */ + public static Optional getPrimaryPath(BibDatabaseContext context, FilePreferences filePreferences) { + List directories = context.getFileDirectories(filePreferences); + if (directories.isEmpty()) { + return Optional.empty(); + } + return Optional.of(directories.getFirst()); } + /** + * Determines if the given relative path is reachable from the primary directory. + * A path is considered reachable if it does not start with ".." (i.e., does not traverse up the directory tree) + * and is not absolute. + * + * @param relativePath the path to check, relative to the primary directory + * @return true if the path is reachable from the primary directory, false otherwise + */ public static boolean isReachableFromPrimaryDirectory(Path relativePath) { try { return !relativePath.startsWith("..") && !relativePath.isAbsolute(); @@ -85,12 +118,91 @@ public static boolean isReachableFromPrimaryDirectory(Path relativePath) { } } - private static Optional getPrimaryPath(BibDatabaseContext targetContext, FilePreferences filePreferences) { - List directories = targetContext.getFileDirectories(filePreferences); - if (directories.isEmpty()) { - return Optional.empty(); + private static boolean isPathAdjusted(LinkedFile linkedFile, Path relative, List linkedFiles, boolean entryChanged) { + boolean pathUpdated = adjustPathForReachableFile( + linkedFile, relative + ); + if (pathUpdated) { + entryChanged = true; } + linkedFiles.add(linkedFile); + return entryChanged; + } - return Optional.of(directories.getFirst()); + private static boolean isFileCopied(FileCopyContext context, LinkedFile linkedFile, boolean entryChanged, List linkedFiles) { + boolean fileCopied = copyFileToTargetContext( + linkedFile, context + ); + if (fileCopied) { + Optional newPath = linkedFile.findIn(context.targetContext(), context.filePreferences()); + newPath.ifPresent(path -> linkedFile.setLink( + FileUtil.relativize(path, context.targetContext(), context.filePreferences()).toString()) + ); + entryChanged = true; + } + linkedFiles.add(linkedFile); + return entryChanged; + } + + /** + * Adjusts the path of a file that is already reachable from the target context. + * + * @return true if the path was updated, false otherwise + */ + private static boolean adjustPathForReachableFile( + LinkedFile linkedFile, + Path relativePath + ) { + try { + String newLink = relativePath.toString(); + String currentLink = linkedFile.getLink(); + + if (!currentLink.equals(newLink)) { + linkedFile.setLink(newLink); + LOGGER.debug("Adjusted path for reachable file: {} -> {}", currentLink, newLink); + return true; + } + } catch (Exception e) { + LOGGER.warn("Failed to adjust path for file {}: {}", linkedFile.getLink(), e.getMessage()); + } + return false; + } + + /** + * Copies a file linked in a `LinkedFile` from the source context to the target context. + * Locates the source file using the source context and file preferences, then copies it to the + * corresponding path in the target context's primary directory, preserving the relative link. + * + * @return true if the file was successfully copied, false otherwise + */ + private static boolean copyFileToTargetContext( + LinkedFile linkedFile, + FileCopyContext context + ) { + Optional sourcePathOpt = linkedFile.findIn(context.sourceContext(), context.filePreferences()); + Optional targetDirOpt = getPrimaryPath(context.targetContext(), context.filePreferences()); + + if (sourcePathOpt.isEmpty() || targetDirOpt.isEmpty()) { + LOGGER.warn("Could not find source file {} for copy", linkedFile.getLink()); + return false; + } + + Path sourcePath = sourcePathOpt.get(); + Path relativeLinkPath = Path.of(linkedFile.getLink()); + Path fullTargetPath = targetDirOpt.get().resolve(relativeLinkPath); + + try { + Files.createDirectories(fullTargetPath.getParent()); + if (Files.exists(fullTargetPath)) { + LOGGER.warn("Target file {} already exists – not overwriting", fullTargetPath); + return false; + } + Files.copy(sourcePath, fullTargetPath); + LOGGER.info("Copied file from {} to {}", sourcePath, fullTargetPath); + return true; + } catch (IOException e) { + LOGGER.error("Failed to copy file from {} to {}: {}", sourcePath, fullTargetPath, e.getMessage()); + return false; + } } } From 86e648c79dec011b22b7ead525f6065e9496ce21 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sun, 13 Jul 2025 23:03:17 +0200 Subject: [PATCH 15/24] test(LinkedFileTransferHelperTest): add tests for all three scenarios --- .../LinkedFileTransferHelper.java | 5 +- .../LinkedFileTransferHelperTest.java | 164 +++++++++++++++--- 2 files changed, 145 insertions(+), 24 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index 90ac8b1b434..d1433bd2c29 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -56,8 +56,8 @@ public static Set adjustLinkedFilesForTarget( continue; } - Optional sourcePathOpt = linkedFile.findIn(context.sourceContext(), context.filePreferences()); - Optional targetPrimaryOpt = getPrimaryPath(context.targetContext(), context.filePreferences()); + Optional sourcePathOpt = linkedFile.findIn(sourceContext, filePreferences); + Optional targetPrimaryOpt = getPrimaryPath(targetContext, filePreferences); if (sourcePathOpt.isEmpty() || targetPrimaryOpt.isEmpty()) { linkedFiles.add(linkedFile); @@ -68,7 +68,6 @@ public static Set adjustLinkedFilesForTarget( if (sourcePathOpt.get().startsWith(targetPrimaryOpt.get())) { relative = targetPrimaryOpt.get().relativize(sourcePathOpt.get()); } else { - // Fallback: mark as unreachable, so the file will be copied relative = Path.of("..").resolve(sourcePathOpt.get().getFileName()); } diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java index 77976222700..05fb717bc0b 100644 --- a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -1,53 +1,175 @@ package org.jabref.logic.externalfiles; import org.jabref.logic.FilePreferences; +import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; class LinkedFileTransferHelperTest { + private BibDatabaseContext sourceContext; + private BibDatabaseContext targetContext; + private Path sourceDir; + private Path targetDir; + private Path testFile; + private BibEntry sourceEntry; + private BibEntry targetEntry; + private FilePreferences filePreferences = mock(FilePreferences.class); @Nested - class IsReachableFromPrimaryDirectoryTest { + @DisplayName("When the linked file is reachable in the new context") + class WhenFileIsReachable { - FilePreferences prefs = mock(FilePreferences.class); - BibDatabaseContext targetContext = mock(BibDatabaseContext.class); + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException { + sourceDir = tempDir.resolve("source/target"); + targetDir = tempDir.resolve("source"); - @Test - void case1_reachableWithinPrimaryDirectory() { - Path targetPrimaryDir = Path.of("/project"); - Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); + + Files.createDirectories(sourceDir); + Files.createDirectories(targetDir); + + testFile = sourceDir.resolve("sourcefiles/test.pdf"); + Files.createDirectories(testFile.getParent()); + Files.createFile(testFile); + + sourceContext = new BibDatabaseContext(new BibDatabase()); + sourceContext.setDatabasePath(sourceDir.resolve("personal.bib")); + targetContext = new BibDatabaseContext(new BibDatabase()); + targetContext.setDatabasePath(targetDir.resolve("papers.bib")); - when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + sourceEntry = new BibEntry(); + LinkedFile linkedFile = new LinkedFile("Test", "sourcefiles/test.pdf", "PDF"); - assertTrue(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + sourceEntry.setFiles(List.of(linkedFile)); + targetEntry = (BibEntry) sourceEntry.clone(); + targetEntry.setFiles(List.of(linkedFile)); + + sourceContext.getDatabase().insertEntry(sourceEntry); + targetContext.getDatabase().insertEntry(targetEntry); } @Test - void case2_sameRelativePathButDifferentBaseDirectory_shouldNotBeReachable() { - Path targetPrimaryDir = Path.of("/project/parentfiles"); - Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) throws IOException { + var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, + filePreferences); + + assertEquals(1, returnedEntries.size()); + assertEquals("sourcefiles/test.pdf", sourceContext.getEntries().getFirst().getFiles().getFirst().getLink()); + assertEquals("target/sourcefiles/test.pdf", + targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); + Path expectedFile = targetDir.resolve("test.pdf"); + assertFalse(Files.exists(expectedFile), "File should not have been copied to target directory"); + } + } + + @Nested + @DisplayName("When the linked file is NOT reachable, but the paths do NOT differ") + class WhenFileIsNotReachable { + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException { + sourceDir = tempDir.resolve("target/targetfiles"); + targetDir = tempDir.resolve("source/sourcefiles"); + + when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); + + Files.createDirectories(sourceDir); + Files.createDirectories(targetDir); + + testFile = sourceDir.resolve("test.pdf"); + Files.createDirectories(testFile.getParent()); + Files.createFile(testFile); + + sourceContext = new BibDatabaseContext(new BibDatabase()); + sourceContext.setDatabasePath(sourceDir.resolve("personal.bib")); + targetContext = new BibDatabaseContext(new BibDatabase()); + targetContext.setDatabasePath(targetDir.resolve("papers.bib")); + + sourceEntry = new BibEntry(); + LinkedFile linkedFile = new LinkedFile("Test", "test.pdf", "PDF"); - when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + sourceEntry.setFiles(List.of(linkedFile)); + targetEntry = (BibEntry) sourceEntry.clone(); + targetEntry.setFiles(List.of(linkedFile)); - assertFalse(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + sourceContext.getDatabase().insertEntry(sourceEntry); + targetContext.getDatabase().insertEntry(targetEntry); } @Test - void case3_differentRelativePathAndDifferentBaseDirectory_shouldNotBeReachable() { - Path targetPrimaryDir = Path.of("/project/parentfiles"); - Path file = Path.of("/project/bibs/personalfiles/file.pdf"); + void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) throws IOException { + var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, + filePreferences); - when(targetContext.getFileDirectories(prefs)).thenReturn(List.of(targetPrimaryDir)); + assertEquals(1, returnedEntries.size()); + assertEquals("test.pdf", sourceContext.getEntries().getFirst().getFiles().getFirst().getLink()); + assertEquals("test.pdf", + targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); + Path expectedFile = targetDir.resolve("test.pdf"); + assertTrue(Files.exists(expectedFile), "File should have been copied to target directory"); + } + } + + @Nested + @DisplayName("When the linked file is NOT reachable, and a nested structure needs to be created") + class WhenFileIsNotReachableAndPathsDiffer { + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException { + sourceDir = tempDir.resolve("source"); + targetDir = tempDir.resolve("target/targetfiles"); + + when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); + + Files.createDirectories(sourceDir); + Files.createDirectories(targetDir); + + testFile = sourceDir.resolve("sourcefiles/test.pdf"); + Files.createDirectories(testFile.getParent()); + Files.createFile(testFile); + + sourceContext = new BibDatabaseContext(new BibDatabase()); + sourceContext.setDatabasePath(sourceDir.resolve("personal.bib")); + targetContext = new BibDatabaseContext(new BibDatabase()); + targetContext.setDatabasePath(targetDir.resolve("papers.bib")); + + sourceEntry = new BibEntry(); + LinkedFile linkedFile = new LinkedFile("Test", "sourcefiles/test.pdf", "PDF"); + + sourceEntry.setFiles(List.of(linkedFile)); + targetEntry = (BibEntry) sourceEntry.clone(); + targetEntry.setFiles(List.of(linkedFile)); + + sourceContext.getDatabase().insertEntry(sourceEntry); + targetContext.getDatabase().insertEntry(targetEntry); + } + + @Test + void fileNotReachableAndPathsDiffer_ShouldCopyFileAndCreateDirectory() throws IOException { + var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, + filePreferences); - assertFalse(LinkedFileTransferHelper.isReachableFromPrimaryDirectory(file, targetContext, prefs)); + assertEquals(1, returnedEntries.size()); + assertEquals("sourcefiles/test.pdf", sourceContext.getEntries().getFirst().getFiles().getFirst().getLink()); + assertEquals("sourcefiles/test.pdf", + targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); + Path expectedFile = targetDir.resolve("sourcefiles/test.pdf"); + assertTrue(Files.exists(expectedFile), "File should have been copied to target directory"); } } } From 24a1e6b02daf2fc8f3b17b969b386e1fadd32a42 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Sun, 13 Jul 2025 23:11:41 +0200 Subject: [PATCH 16/24] docs(requirements): add file transfer requirements specification --- docs/requirements/filed.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/requirements/filed.md diff --git a/docs/requirements/filed.md b/docs/requirements/filed.md new file mode 100644 index 00000000000..145279fa977 --- /dev/null +++ b/docs/requirements/filed.md @@ -0,0 +1,21 @@ +# File Transfer Between Bib Entries + +### File is reachable and should not be copied +`req~logic.externalfiles.file-transfer.reachable-no-copy~1` +When a linked file is reachable from the target context, the system must adjust the relative path in the target entry but must not copy the file again. + +Needs: impl + +### File is not reachable, but the path is the same +`req~logic.externalfiles.file-transfer.not-reachable-same-path~1` +When a linked file is not reachable from the target context, and the relative path in both source and target entry is the same, the file must be copied to the target context. + +Needs: impl + +### File is not reachable, and a different path is used +`req~logic.externalfiles.file-transfer.not-reachable-different-path~1` +When a linked file is not reachable from the target context, and the relative path differs between source and target entries, the file must be copied and the directory structure must be created to preserve the relative link. + +Needs: impl + + From aaebd4873ff9746a30b7c0a6e97289c5bdba28cd Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:07:15 +0200 Subject: [PATCH 17/24] chore: remove unnecessary comments and adjust filed.ms to increment the heading leven only by one --- docs/requirements/filed.md | 6 +++--- jabgui/src/main/java/org/jabref/gui/LibraryTab.java | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/requirements/filed.md b/docs/requirements/filed.md index 145279fa977..e3679f7325a 100644 --- a/docs/requirements/filed.md +++ b/docs/requirements/filed.md @@ -1,18 +1,18 @@ # File Transfer Between Bib Entries -### File is reachable and should not be copied +## File is reachable and should not be copied `req~logic.externalfiles.file-transfer.reachable-no-copy~1` When a linked file is reachable from the target context, the system must adjust the relative path in the target entry but must not copy the file again. Needs: impl -### File is not reachable, but the path is the same +## File is not reachable, but the path is the same `req~logic.externalfiles.file-transfer.not-reachable-same-path~1` When a linked file is not reachable from the target context, and the relative path in both source and target entry is the same, the file must be copied to the target context. Needs: impl -### File is not reachable, and a different path is used +## File is not reachable, and a different path is used `req~logic.externalfiles.file-transfer.not-reachable-different-path~1` When a linked file is not reachable from the target context, and the relative path differs between source and target entries, the file must be copied and the directory structure must be created to preserve the relative link. diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index 788f72206c8..578e2927e0d 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -854,10 +854,8 @@ private void copyEntriesWithFeedback(List entriesToAdd) { } }); - // Import mit Duplikat-Check durchführen importHandler.importEntriesWithDuplicateCheck(bibDatabaseContext, finalEntriesToAdd, tracker); - // Linked Files anpassen falls Source-Context vorhanden if (clipBoardManager.getSourceBibDatabaseContext().isPresent()) { tracker.setOnFinish(() -> LinkedFileTransferHelper .adjustLinkedFilesForTarget(clipBoardManager.getSourceBibDatabaseContext().get(), From 5e32e8b97875aeabb373cfadb61af8a8f43aa402 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:10:44 +0200 Subject: [PATCH 18/24] chore: replace Collection constructors with factories --- .../jabref/logic/externalfiles/LinkedFileTransferHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index d1433bd2c29..5b552f6694b 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -42,13 +42,13 @@ public static Set adjustLinkedFilesForTarget( FilePreferences filePreferences ) { - Set modifiedEntries = new HashSet<>(); + Set modifiedEntries = Set.of(); FileCopyContext context = new FileCopyContext(sourceContext, targetContext, filePreferences); for (BibEntry entry : targetContext.getEntries()) { boolean entryChanged = false; - List linkedFiles = new ArrayList<>(); + List linkedFiles = List.of(); for (LinkedFile linkedFile : entry.getFiles()) { if (linkedFile == null || linkedFile.getLink().isEmpty()) { From 3e54bc76873eb62b94ad3a5ab387951acf748d42 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:11:57 +0200 Subject: [PATCH 19/24] chore: remove DisplayNames for test classes --- .../logic/externalfiles/LinkedFileTransferHelperTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java index 05fb717bc0b..673d80cd3d3 100644 --- a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -30,7 +30,6 @@ class LinkedFileTransferHelperTest { private FilePreferences filePreferences = mock(FilePreferences.class); @Nested - @DisplayName("When the linked file is reachable in the new context") class WhenFileIsReachable { @BeforeEach @@ -78,7 +77,6 @@ void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) throws IOException { } @Nested - @DisplayName("When the linked file is NOT reachable, but the paths do NOT differ") class WhenFileIsNotReachable { @BeforeEach @@ -126,7 +124,6 @@ void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) throws IOException { } @Nested - @DisplayName("When the linked file is NOT reachable, and a nested structure needs to be created") class WhenFileIsNotReachableAndPathsDiffer { @BeforeEach From ee96a8587e8f1ffcd8210a1863477ea612a27d76 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:15:43 +0200 Subject: [PATCH 20/24] chore: replace assertTrue with assertEquals --- .../logic/externalfiles/LinkedFileTransferHelperTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java index 673d80cd3d3..d6f964dde01 100644 --- a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -72,7 +72,7 @@ void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) throws IOException { assertEquals("target/sourcefiles/test.pdf", targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); Path expectedFile = targetDir.resolve("test.pdf"); - assertFalse(Files.exists(expectedFile), "File should not have been copied to target directory"); + assertEquals(true, !Files.exists(expectedFile)); } } @@ -119,7 +119,7 @@ void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) throws IOException { assertEquals("test.pdf", targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); Path expectedFile = targetDir.resolve("test.pdf"); - assertTrue(Files.exists(expectedFile), "File should have been copied to target directory"); + assertEquals(true, Files.exists(expectedFile)); } } @@ -166,7 +166,7 @@ void fileNotReachableAndPathsDiffer_ShouldCopyFileAndCreateDirectory() throws IO assertEquals("sourcefiles/test.pdf", targetContext.getEntries().getFirst().getFiles().getFirst().getLink()); Path expectedFile = targetDir.resolve("sourcefiles/test.pdf"); - assertTrue(Files.exists(expectedFile), "File should have been copied to target directory"); + assertEquals(true, Files.exists(expectedFile)); } } } From 2e8125bc27466600d5322ea3dfa8cb3ce50c3617 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:24:14 +0200 Subject: [PATCH 21/24] refactor: change method signature to have boolean parameter last and change to collection constructors again --- .../logic/externalfiles/LinkedFileTransferHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index 5b552f6694b..8abbd3fa97c 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -42,13 +42,13 @@ public static Set adjustLinkedFilesForTarget( FilePreferences filePreferences ) { - Set modifiedEntries = Set.of(); + Set modifiedEntries = new HashSet<>(); FileCopyContext context = new FileCopyContext(sourceContext, targetContext, filePreferences); for (BibEntry entry : targetContext.getEntries()) { boolean entryChanged = false; - List linkedFiles = List.of(); + List linkedFiles = new ArrayList<>(); for (LinkedFile linkedFile : entry.getFiles()) { if (linkedFile == null || linkedFile.getLink().isEmpty()) { @@ -74,7 +74,7 @@ public static Set adjustLinkedFilesForTarget( if (isReachableFromPrimaryDirectory(relative)) { entryChanged = isPathAdjusted(linkedFile, relative, linkedFiles, entryChanged); } else { - entryChanged = isFileCopied(context, linkedFile, entryChanged, linkedFiles); + entryChanged = isFileCopied(context, linkedFile, linkedFiles, entryChanged); } } if (entryChanged) { @@ -128,7 +128,7 @@ private static boolean isPathAdjusted(LinkedFile linkedFile, Path relative, List return entryChanged; } - private static boolean isFileCopied(FileCopyContext context, LinkedFile linkedFile, boolean entryChanged, List linkedFiles) { + private static boolean isFileCopied(FileCopyContext context, LinkedFile linkedFile, List linkedFiles, boolean entryChanged) { boolean fileCopied = copyFileToTargetContext( linkedFile, context ); From f2bcebbd4c15a0b972a2437fbbd958be127944c4 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:26:58 +0200 Subject: [PATCH 22/24] refactor: replace IOException with Exception in the throws clause and remove unnecessary throws clauses --- .../externalfiles/LinkedFileTransferHelperTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java index d6f964dde01..a998d946e4b 100644 --- a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -33,7 +33,7 @@ class LinkedFileTransferHelperTest { class WhenFileIsReachable { @BeforeEach - void setup(@TempDir Path tempDir) throws IOException { + void setup(@TempDir Path tempDir) throws Exception { sourceDir = tempDir.resolve("source/target"); targetDir = tempDir.resolve("source"); @@ -63,7 +63,7 @@ void setup(@TempDir Path tempDir) throws IOException { } @Test - void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) throws IOException { + void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) { var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, filePreferences); @@ -80,7 +80,7 @@ void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) throws IOException { class WhenFileIsNotReachable { @BeforeEach - void setup(@TempDir Path tempDir) throws IOException { + void setup(@TempDir Path tempDir) throws Exception { sourceDir = tempDir.resolve("target/targetfiles"); targetDir = tempDir.resolve("source/sourcefiles"); @@ -110,7 +110,7 @@ void setup(@TempDir Path tempDir) throws IOException { } @Test - void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) throws IOException { + void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) { var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, filePreferences); @@ -127,7 +127,7 @@ void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) throws IOException { class WhenFileIsNotReachableAndPathsDiffer { @BeforeEach - void setup(@TempDir Path tempDir) throws IOException { + void setup(@TempDir Path tempDir) throws Exception { sourceDir = tempDir.resolve("source"); targetDir = tempDir.resolve("target/targetfiles"); @@ -157,7 +157,7 @@ void setup(@TempDir Path tempDir) throws IOException { } @Test - void fileNotReachableAndPathsDiffer_ShouldCopyFileAndCreateDirectory() throws IOException { + void fileNotReachableAndPathsDiffer_ShouldCopyFileAndCreateDirectory() { var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, filePreferences); From 5a23b6fe371ba35fc3c004e736133efdc49d937d Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:30:25 +0200 Subject: [PATCH 23/24] refactor: remove unused import --- .../logic/externalfiles/LinkedFileTransferHelperTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java index a998d946e4b..84748b9cc70 100644 --- a/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java +++ b/jablib/src/test/java/org/jabref/logic/externalfiles/LinkedFileTransferHelperTest.java @@ -6,12 +6,10 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -63,7 +61,7 @@ void setup(@TempDir Path tempDir) throws Exception { } @Test - void pathDiffers_ShouldAdjustPath(@TempDir Path tempDir) { + void pathDiffers_ShouldAdjustPath() { var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, filePreferences); @@ -110,7 +108,7 @@ void setup(@TempDir Path tempDir) throws Exception { } @Test - void fileNotReachable_ShouldCopyFile(@TempDir Path tempDir) { + void fileNotReachable_ShouldCopyFile() { var returnedEntries = LinkedFileTransferHelper.adjustLinkedFilesForTarget(sourceContext, targetContext, filePreferences); From f2b46a96ee9821359e107495b3352e509c754903 Mon Sep 17 00:00:00 2001 From: Umut Akbayin Date: Mon, 14 Jul 2025 00:32:28 +0200 Subject: [PATCH 24/24] refactor: remove static modifier --- .../jabref/logic/externalfiles/LinkedFileTransferHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java index 8abbd3fa97c..ed26d8c26a2 100644 --- a/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java +++ b/jablib/src/main/java/org/jabref/logic/externalfiles/LinkedFileTransferHelper.java @@ -19,7 +19,7 @@ public class LinkedFileTransferHelper { - private static record FileCopyContext( + private record FileCopyContext( BibDatabaseContext sourceContext, BibDatabaseContext targetContext, FilePreferences filePreferences