Skip to content

Relink moved/renamed files#13326

Merged
calixtus merged 10 commits into
JabRef:mainfrom
lawmeyian:fix-relink-moved-files-13264
Jun 15, 2025
Merged

Relink moved/renamed files#13326
calixtus merged 10 commits into
JabRef:mainfrom
lawmeyian:fix-relink-moved-files-13264

Conversation

@lawmeyian

Copy link
Copy Markdown
Contributor

relink moved/renamed files when auto-searching for attachments

Closes #13264

When “Automatically search and show unlinked files” is enabled, JabRef now searches by base file name if the original link is broken. This lets JabRef relink attachments that users have moved or renamed inside the configured file directory, instead of showing them as “not found”.

Steps to test

  1. Preparation
  • Open any database with a file directory set.
  • Attach a PDF (e.g. minimal.pdf) to an entry via the “General” tab.
  • Close the entry editor.
  1. Simulate a move
  • Outside JabRef, move the file from
    …/old/minimal.pdf…/new/minimal.pdf (same tree).
  • Re-open the entry editor – the link should show orange (“file not found”).
  1. Trigger auto-search
  • Ensure Options → Preferences → Linked files → Automatically search and show unlinked files is checked.
  • Reload the library or reopen the entry.
  • The orange state disappears and the file is relinked to the new path new/minimal.pdf.

Mandatory checks

  • I own the copyright of the code submitted and I license it under the MIT license
  • Change in CHANGELOG.md described in a way that is understandable for the average user (if change is visible to the user)
  • Tests created for changes (if applicable)
  • Manually tested changed features in running JabRef (always required)
  • [/] Screenshots added in PR description (if change is visible to the user)
  • Checked developer's documentation: Is the information available and up to date? If not, I outlined it in this pull request.
  • [/] Checked 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 to the documentation repository.

Comment thread jabgui/src/main/java/org/jabref/gui/externalfiles/AutoSetFileLinksUtil.java Outdated
@calixtus calixtus changed the title fixes #13264 relink moved/renamed files Relink moved/renamed files Jun 14, 2025
@calixtus calixtus added the status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers label Jun 15, 2025
@Siedlerchr

Copy link
Copy Markdown
Member

After restaring jabref I get an exception when I try this autolink:

**java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = pool-2-thread-4
	at javafx.graphics@24.0.1/com.sun.glass.ui.Application.checkEventThread(Application.java:442)
	at javafx.graphics@24.0.1/com.sun.glass.ui.MenuItem.setEnabled(MenuItem.java:108)
	at javafx.graphics@24.0.1/com.sun.javafx.tk.quantum.GlassSystemMenu.lambda$insertMenuItem$8(GlassSystemMenu.java:289)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:78)
	at javafx.base@24.0.1/javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/javafx.beans.binding.Bindings$ShortCircuitAndInvalidator.invalidated(Bindings.java:4669)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.FlatMapBindingBase.mappedInvalidated(FlatMap.java:72)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.FlatMapBindingBase.lambda$new$1(FlatMap.java:22)
	at javafx.base@24.0.1/javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:82)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.collections.MapListenerHelper$Generic.fireValueChangedEvent(MapListenerHelper.java:318)
	at javafx.base@24.0.1/com.sun.javafx.collections.MapListenerHelper.fireValueChangedEvent(MapListenerHelper.java:70)
	at javafx.base@24.0.1/com.sun.javafx.collections.ObservableMapWrapper.callObservers(ObservableMapWrapper.java:115)
	at javafx.base@24.0.1/com.sun.javafx.collections.ObservableMapWrapper.put(ObservableMapWrapper.java:169)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setField(BibEntry.java:627)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setField(BibEntry.java:645)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setFiles(BibEntry.java:1071)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.addFile(BibEntry.java:1099)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.lambda$new$0(AutoLinkFilesAction.java:70)
	at org.jabref/org.jabref.gui.externalfiles.AutoSetFileLinksUtil.linkAssociatedFiles(AutoSetFileLinksUtil.java:85)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.call(AutoLinkFilesAction.java:75)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.call(AutoLinkFilesAction.java:63)
	at javafx.graphics@24.0.1/javafx.concurrent.Task$TaskCallable.call(Task.java:1388)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1095)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:619)
	at java.base/java.lang.Thread.run(Thread.java:1447)

@Siedlerchr

Copy link
Copy Markdown
Member
java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = pool-2-thread-4
	at javafx.graphics@24.0.1/com.sun.glass.ui.Application.checkEventThread(Application.java:442)
	at javafx.graphics@24.0.1/com.sun.glass.ui.MenuItem.setEnabled(MenuItem.java:108)
	at javafx.graphics@24.0.1/com.sun.javafx.tk.quantum.GlassSystemMenu.lambda$insertMenuItem$8(GlassSystemMenu.java:289)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.fireValueChangedEvent(BooleanPropertyBase.java:104)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.property.ReadOnlyBooleanPropertyBase.fireValueChangedEvent(ReadOnlyBooleanPropertyBase.java:78)
	at javafx.base@24.0.1/javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:103)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:111)
	at javafx.base@24.0.1/javafx.beans.property.BooleanPropertyBase$Listener.invalidated(BooleanPropertyBase.java:239)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/javafx.beans.binding.Bindings$ShortCircuitAndInvalidator.invalidated(Bindings.java:4669)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:376)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.ObjectBinding.invalidate(ObjectBinding.java:192)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.FlatMapBindingBase.mappedInvalidated(FlatMap.java:72)
	at com.tobiasdiez.easybind@2.2.1-SNAPSHOT/com.tobiasdiez.easybind.FlatMapBindingBase.lambda$new$1(FlatMap.java:22)
	at javafx.base@24.0.1/javafx.beans.WeakInvalidationListener.invalidated(WeakInvalidationListener.java:82)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:147)
	at javafx.base@24.0.1/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:91)
	at javafx.base@24.0.1/javafx.beans.binding.BooleanBinding.invalidate(BooleanBinding.java:180)
	at javafx.base@24.0.1/com.sun.javafx.binding.BindingHelperObserver.invalidated(BindingHelperObserver.java:52)
	at javafx.base@24.0.1/com.sun.javafx.collections.MapListenerHelper$Generic.fireValueChangedEvent(MapListenerHelper.java:318)
	at javafx.base@24.0.1/com.sun.javafx.collections.MapListenerHelper.fireValueChangedEvent(MapListenerHelper.java:70)
	at javafx.base@24.0.1/com.sun.javafx.collections.ObservableMapWrapper.callObservers(ObservableMapWrapper.java:115)
	at javafx.base@24.0.1/com.sun.javafx.collections.ObservableMapWrapper.put(ObservableMapWrapper.java:169)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setField(BibEntry.java:627)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setField(BibEntry.java:645)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.setFiles(BibEntry.java:1071)
	at org.jabref.jablib/org.jabref.model.entry.BibEntry.addFile(BibEntry.java:1099)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.lambda$new$0(AutoLinkFilesAction.java:70)
	at org.jabref/org.jabref.gui.externalfiles.AutoSetFileLinksUtil.linkAssociatedFiles(AutoSetFileLinksUtil.java:85)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.call(AutoLinkFilesAction.java:75)
	at org.jabref/org.jabref.gui.externalfiles.AutoLinkFilesAction$1.call(AutoLinkFilesAction.java:63)
	at javafx.graphics@24.0.1/javafx.concurrent.Task$TaskCallable.call(Task.java:1388)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1095)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:619)
	at java.base/java.lang.Thread.run(Thread.java:1447)

@trag-bot

trag-bot Bot commented Jun 15, 2025

Copy link
Copy Markdown

@trag-bot didn't find any issues in the code! ✅✨

@calixtus calixtus enabled auto-merge June 15, 2025 19:59
@calixtus calixtus added this pull request to the merge queue Jun 15, 2025
Merged via the queue into JabRef:main with commit d8b2e71 Jun 15, 2025
1 check passed
@lawmeyian

Copy link
Copy Markdown
Contributor Author

Thank you @calixtus and @Siedlerchr for your helpful feedback and support throughout this PR. I really appreciate the time you took to review my code, point out improvements, and guide me through the fixes. I’ve learned a lot from this process!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If user moved file, it should simply be relinked - [Entry Editor part]

3 participants