Skip to content

Conversation

UmutAkbayin
Copy link

@UmutAkbayin UmutAkbayin commented Jul 13, 2025

Closes #12267

Adds a requirements specification file files.md documenting the linked file transfer logic between BibTeX entries in JabRef.
The document captures three key scenarios for when linked files are reachable or not in the target context and the expected behavior regarding path adjustment and file copying.

This improves traceability by formally specifying requirements that are linked to implementation and tests via OpenFastTrace.

Steps to test

  • Verify that the new requirements specification file docs/requirements/files.md is present, properly formatted, and follows JabRef conventions.

  • Run all existing and new unit tests in LinkedFileTransferHelperTest to ensure the linked file transfer logic behaves correctly across all covered scenarios.

  • Manually verify the business logic in JabRef, especially for the scenario where the linked file is not reachable and a nested directory structure must be created. Reviewers should confirm that the path adjustments and file copying behavior conform to the requirements.

  • Provide feedback if the handling of the last case (unreachable file with differing paths) aligns with project expectations.

CopyFileOption

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • I described the change in CHANGELOG.md in a way that is understandable for the average user (if change is visible to the user)
  • [/] I checked the user documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request updating file(s) in https://github.com/JabRef/user-documentation/tree/main/en.

FilePreferences filePreferences
) {

Set<BibEntry> modifiedEntries = new HashSet<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using constructor for HashSet instead of modern Java collection factory method Set.of(). Modern Java practices recommend using factory methods for cleaner and more maintainable code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could not find a quick solution. would have to refactor the logic then.


for (BibEntry entry : targetContext.getEntries()) {
boolean entryChanged = false;
List<LinkedFile> linkedFiles = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using ArrayList constructor instead of modern Java collection factory methods. Should use List.of() for empty list initialization following JabRef's conventions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could not find a quick solution. would have to refactor the logic then.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TracBot is wrong here 😅

@koppor
Copy link
Member

koppor commented Jul 23, 2025

@UmutAkbayin Please read the bot comment - one week old

Fix the tests

image

@koppor koppor marked this pull request as draft July 23, 2025 19:18
@koppor
Copy link
Member

koppor commented Jul 23, 2025

I converted the PR to draft, because a) merge conflicts b) tests failing.

@koppor
Copy link
Member

koppor commented Jul 23, 2025

"Steps to test" seems to be AI generated. It is not a step-by-step guide. Please refine.

@UmutAkbayin
Copy link
Author

I noticed that one unit test is affected by the changes in LibraryTab#pasteEntry. I’ll look into it tomorrow or over the weekend. I’ll also review the other things like code style.

@UmutAkbayin UmutAkbayin force-pushed the fix/copy-linked-files-on-entry-transfer branch from 3f155a7 to 3ee3b08 Compare August 1, 2025 19:38
Umut Akbayin added 15 commits August 1, 2025 21:55
Allows tracking the source BibDatabaseContext for later use in pasteEntry()
…er and setter

Enables tracking the source database context for use in pasteEntry()
… CopyTo#copyEntriesWithFeedback and LibraryTab#pasteEntry
Add filePreferences parameter to CopyTo.java to enable
LinkedFile#findIn functionality
…sferHelperTest#isReachableFromPrimaryDirectory method
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.
…ls in LibraryTab and CopyTo to align with new method signature
@UmutAkbayin
Copy link
Author

It looks like the LibraryTab part stopped working after the merge. Currently only CopyTo works, while copy & paste and drag & drop don’t. I’ll take a closer look at it.

Umut Akbayin added 7 commits August 27, 2025 19:46
Previously, EditAction created a new ClipBoardManager, which caused copy, cut, and paste to not use the shared manager. Now EditAction uses the shared ClipBoardManager, fixing copy/paste and drag & drop behavior in the right-click menu and elsewhere.
@UmutAkbayin
Copy link
Author

UmutAkbayin commented Aug 27, 2025

Originally, EditAction created a new ClipBoardManager, which caused copy, cut, and paste to not use the shared manager. This was my fault and has been fixed by using the existing ClipBoardManager. Copy/paste work correctly in the right-click menu and with keys.

Drag and Drop actually never worked, but I have fixed it now. I have passed the BibDatabaseContext in FrameDndHandler. Seems to work, but I trust the reviewer. Sorry, I had to experiment a little bit.

Note: This issue was not detected by unit tests, because the code never reached the helper class. It could be covered by integration tests in the future.

During testing on my NAS device, I noticed that deleting files via JabRef sometimes fails, and copied PDFs can occasionally become corrupted, while text files work fine. This appears to be related to NAS file permissions or network file system behavior, not the JabRef logic itself.

Situation in setup:
Screenshot 2025-08-27 at 18 17 26

and after the paste:
Screenshot 2025-08-27 at 19 46 22

I thinks this is the desired behavior.

@UmutAkbayin
Copy link
Author

Only PR Checklist seems to fail. The layout has changed I guess, I tried to adjust it.

Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some code adapations.

Some comments.

I ttoally forgot in the issue to ask for a preference. Default should be "true". See #13295 how to add a preference. You can also use "Linked files" preferences.

Comment on lines 114 to 116
tracker.setOnFinish(() -> LinkedFileTransferHelper
.adjustLinkedFilesForTarget(sourceDatabaseContext, targetDatabaseContext, filePreferences)
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is assuming that "importHandler" in the line before (!) executes asynchronously.

It overwrites the handler set in line 96.

Did you play around here?

Should we just remove lines 96 to 111?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to correct this like in LibraryTab. I think it could work inside the first handler.

case COPY -> tabSupplier.get().copyEntry();
case CUT -> tabSupplier.get().cutEntry();
case COPY -> {
clipBoardManager.setSourceBibDatabaseContext(tabSupplier.get().getBibDatabaseContext());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be included in copyEntry

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. done.

tabSupplier.get().copyEntry();
}
case CUT -> {
clipBoardManager.setSourceBibDatabaseContext(tabSupplier.get().getBibDatabaseContext());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be included in cutEntry

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. done.

@@ -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 = new BibEntry(cleanUpEntry(bibDatabaseContext, entry));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short Java comemnt above. Maybe

// The original entry should not be modified

Please do the copy before cleanUpEntry - otherwise the source entry will be modified, too.

Comment on lines 81 to 83
case COPY -> {
tabSupplier.get().copyEntry();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block arrow syntax with curly braces is unnecessarily verbose for a single statement. This reduces code readability and violates the principle of keeping code concise.

Comment on lines 84 to 86
case CUT -> {
tabSupplier.get().cutEntry();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block arrow syntax with curly braces is unnecessarily verbose for a single statement. This reduces code readability and violates the principle of keeping code concise.

@UmutAkbayin
Copy link
Author

I have adjusted everything except for the preferences part. I will take another look at that later.

I had to move the entry copy to the cleanUpEntry method, otherwise a non-final variable would be used in the lambda expression.

Umut Akbayin added 3 commits August 28, 2025 21:50
…le preferences

Users should be able to disable the automatic file adjustment/copying during transfers
via a preference setting (default: enabled).
@UmutAkbayin
Copy link
Author

I added the File Preferences feature, as well as another unit test, and tested it manually.


for (BibEntry entry : targetContext.getEntries()) {
boolean entryChanged = false;
List<LinkedFile> linkedFiles = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayList constructor usage instead of modern Java collection factory methods makes code less maintainable and readable.

@UmutAkbayin UmutAkbayin requested a review from koppor August 30, 2025 12:54
@koppor koppor changed the title Fix/copy linked files on entry transfer Copy linked files on entry transfer Aug 31, 2025
Copy link
Member

@koppor koppor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments. I need several hours for revewing this. Hope to find time for it on Wednesday.

@@ -279,8 +280,10 @@ private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext

@VisibleForTesting
BibEntry cleanUpEntry(BibDatabaseContext bibDatabaseContext, BibEntry entry) {
// The original entry should not be modified
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this - the comment is already at line 254.

@@ -279,8 +280,10 @@ private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext

@VisibleForTesting
BibEntry cleanUpEntry(BibDatabaseContext bibDatabaseContext, BibEntry entry) {
// The original entry should not be modified
BibEntry entryCopy = new BibEntry(entry);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to line 255 (in the calling method)

Add JavaDoc that the entry at hand is modified.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean I should add JavaDoc to the cleanUpEntry method? I am not the author of this method, should I still add the JavaDoc?
I will add a JavaDoc to the importEntryWithDuplicateCheck method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -106,7 +107,8 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T
if (hasEntries(dragboard)) {
List<BibEntry> entryCopies = stateManager.getLocalDragboard().getBibEntries().stream()
.map(BibEntry::new).toList();
destinationLibraryTab.dropEntry(entryCopies);
BibDatabaseContext sourceBibDatabaseContext = stateManager.getActiveDatabase().orElse(null);
destinationLibraryTab.dropEntry(entryCopies, sourceBibDatabaseContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap the parameters to preserve the order source, target (aspects).

Comment on lines -155 to +156
importHandler, sourceDatabaseContext, targetDatabaseContext),
preferences.getFilePreferences(), importHandler, sourceDatabaseContext,
targetDatabaseContext),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent seems to be off somehow

copyEntriesWithFeedback(entriesToAdd, sourceBibDatabaseContext);
}

private void copyEntriesWithFeedback(List<BibEntry> entriesToAdd, BibDatabaseContext sourceBibDatabaseContext) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nullable should be annotated

The method is a nearly 100% code duplication from org.jabref.gui.edit.CopyTo#copyEntriesWithFeedback

The version of org.jabref.gui.edit.CopyTo#copyEntriesWithFeedback is not that good, because the non-standard use of Localization.lang should at least be documented.

Move the method to package org.jabref.gui.util. You can create a class CopyUtil with a static method.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. Just to clarify - this method is code that I reused from the existing org.jabref.gui.edit.CopyTo#copyEntriesWithFeedback method (originally implemented by another author]). I copied it because I needed the same functionality in my code.
Since this represents code duplication as you noted, should I still go ahead and refactor it into the org.jabref.gui.util.CopyUtil class as suggested? This would benefit both the original location and my usage, and I'm happy to do the refactoring work even though I wasn't the original author. Just asking, because I don't want to 'steal' the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be not a thing such as authorship for methods... The aim of the community is to have the code in a good state. We are in the open source world; there should be a step back from author ship - its not art what we do here, its engineering :)

Thus, please, go ahead with refactoring.

Comment on lines +870 to +871
dialogService.notify(Localization.lang("Pasted %0 entry(s) to %1",
String.valueOf(importedCount), targetName));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent is off

if (entriesToAdd.isEmpty()) {
entriesToAdd = handleNonBibTeXStringData(content);
}
if (entriesToAdd.isEmpty()) {
return;
}
BibDatabaseContext sourceBibDatabaseContext = clipBoardManager.getSourceBibDatabaseContext().orElse(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment before needs to be added

// Now, the BibEntries to add are known
// The definitive insertion needs to happen now.

Optional<Path> sourcePathOpt = linkedFile.findIn(sourceContext, filePreferences);
Optional<Path> targetPrimaryOpt = getPrimaryPath(targetContext, filePreferences);

if (sourcePathOpt.isEmpty() || targetPrimaryOpt.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment above

// In case the file wasn't found at the source or the target - just re-use the existing link

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this if. Why is the thing stopping if the fiel is not found in the targetPrimaryPath? There is no copy made before (is it?)

if (sourcePathOpt.get().startsWith(targetPrimaryOpt.get())) {
relative = targetPrimaryOpt.get().relativize(sourcePathOpt.get());
} else {
relative = Path.of("..").resolve(sourcePathOpt.get().getFileName());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this case ever hit? According to org.jabref.logic.util.io.FileUtil#find(java.lang.String, java.nio.file.Path), findIn always returns an aboslute path.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in this part is from quite a while ago, so I’d like to take some time tomorrow to read back into it before responding in detail. I also noticed an issue in my branch (a RepositoryNotFoundException when switching tabs – only visible in the logs, work can still continue), and I’d like to investigate that in the same context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - RepositoryNotFoundException is known and should be fixed soon (wanling0000#11). Background: We merged some updates of the git behavior into the main branch early. -- No need to investigate 😅

One could check with test coverage if this line is hit by test cases.

Sorry for the long delays while reviewing. Code review takes much time and all of this happens in our free time.

}

Optional<Path> sourcePathOpt = linkedFile.findIn(sourceContext, filePreferences);
Optional<Path> targetPrimaryOpt = getPrimaryPath(targetContext, filePreferences);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, why not findIn is used here, too. --> if this returns something, the link does not need to be adjusted at all - because it is still found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: changes-required Pull requests that are not yet complete
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When copying and pasting an entry to another bib file - all attached files should also be copied
3 participants