Skip to content

Conversation

@ErwanGou
Copy link

@ErwanGou ErwanGou commented Nov 7, 2025

Closes #10930

Changed or Added files

  • DirectoryGroup.java
  • DirectoryGroupTest.java
  • GroupNodeViewModel.java
  • GroupDialog.fxml
  • GroupDialogView.java
  • GroupDialogViewModel.java
  • GroupDialogViewModelTest.java
  • JabRef_en.properties
  • DirectoryUpdateListener.java
  • DirectoryUpdateMonitor.java
  • DummyDirectoryUpdateMonitor.java
  • DefaultDirectoryUpdateMonitor.java
  • JabRefGUI.java
  • GroupTreeViewModel.java
  • GroupTreeViewModelTest.java
  • GroupTreeView.java
  • CHANGELOG.md

The class DirectoryGroup.java contains the new type of group. There are some tests in DirectoryGroupTest.java but most of the feature cannot be tested because it needs the WatchService.

The changes in GroupNodeViewModel.java allow directory groups to be edited or removed. The root node of the mirrored structure can be dragged.

The changes in GroupDialog.fxml, GroupDialogView.java and GroupDialogViewModel.java add the button to mirror the user's local structure within JabRef. The GroupDialogViewModelTest.java was updated to fit the new constructor and to test the new feature.

The new version of JabRef_en.properties contains the English text displayed to the user when trying to mirror its local structure.

The files DirectoryUpdateListener.java, DirectoryUpdateMonitor.java, DummyDirectoryUpdateMonitor.java and DefaultDirectoryUpdateMonitor.java implement a watcher for directories. The file JabRefGUI.java is now able to initialize this watcher.

The file GroupTreeViewModel.java can now create a directory structure. It needed new attributes in the constructor so GroupTreeViewModelTest.java has been updated.

The changes in GroupTreeView.java correct a bug : the user could use "Sort subgroups Z-A" on groups that are not sortable.

In the file CHANGELOG.md there is the description of what was changed in the code.

Next steps

There is a slight issue with the watcher on Windows : the user can't rename or move a local directory that is mirrored and not empty because it is detected as opened in an application. However, the deletion is working properly so we can bypass this issue.

I chose not to allow the user to add entries in a directory group because it is supposed to work only automatically, but due to this choice they are not sortable. About this point I think the existing implementation of sortAlphabeticallyRecursive() and sortReverseAlphabeticallyRecursive() in GroupTreeViewModel.java is a bit weird :

  • It is recursive but never checks if the subgroup is sortable so we can actually sort Directory Groups if we sort the AllEntries node.
  • Furthermore I didn't get why it is mandatory that "group.canAddEntriesIn()" should be true because it seems the sorted method never uses that.

Steps to test

Start JabRef and open a library, empty or not :
image

You can add a new group by clicking on the 'Add group' button. A dialog should show up. Select 'Directory structure' under the 'Collect by' title :
image

Here you can select the root folder of the structure you want to mirror by clicking on the folder icon button :
image

Once the root is set it should automatically fill the 'Root path'. It also fills the 'Name' of the group if and only if it was empty and set the hierarchical context to 'Union', which means a group contains the entries of its subgroups -it is not mandatory to use that but it is recommended because it is how local folders work. You can also write or paste the root path instead of selecting the folder but remember that the 'OK' button won't be active until a valid path and a valid name are provided. You can add a description, an icon, a color or even change the hierarchical context if you want :
image

Then click on the 'OK' button, your local structure should appear :
image

The entries created with the local PDFs should appear a little while later :
image

Now feel free to test the feature. You can either add, delete, rename or move PDFs or folders in your local directory structure, you should see the consequences on your library groups. You can also edit those groups with a right-click. Remember that if you remove a group or change its type it won't adapt to your local changes anymore. If you create an entry that has a file within the mirrored structure it should automatically be added to the respective group. The feature also works if you have multiple opened libraries.

Mandatory checks

ErwanGou and others added 25 commits November 3, 2025 15:16
…x-issue-10930

# Conflicts:
#	jabgui/src/main/java/org/jabref/gui/groups/GroupDialogView.java
#	jabgui/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java
#	jabgui/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java
#	jabgui/src/main/resources/org/jabref/gui/groups/GroupDialog.fxml
@github-actions
Copy link
Contributor

github-actions bot commented Nov 7, 2025

Hey @ErwanGou!

Thank you for contributing to JabRef! Your help is truly appreciated ❤️.

We have automatic checks in place, based on which you will soon get automated feedback if any of them are failing. We also use TragBot with custom rules that scans your changes and provides some preliminary comments, before a maintainer takes a look. TragBot is still learning, and may not always be accurate. In the "Files changed" tab, you can go through its comments and just click on "Resolve conversation" if you are sure that it is incorrect, or comment on the conversation if you are doubtful.

Please re-check our contribution guide in case of any other doubts related to our contribution workflow.

JabRefGUI.dialogService = new JabRefDialogService(mainStage);
Injector.setModelOrService(DialogService.class, dialogService);

DefaultDirectoryUpdateMonitor directoryUpdateMonitor = new DefaultDirectoryUpdateMonitor(new BibDatabaseContext(), preferences, fileUpdateMonitor, countingUndoManager, stateManager, dialogService, taskExecutor);
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove the local variable and assign the new object to JabRefGUI.directoryUpdateMonitor, and then just use directoryUpdateMonitor, because you are already in JabRefGUI. This is just so the code looks the same manner as previous code

Copy link
Author

Choose a reason for hiding this comment

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

I tried this first but then I got an error in line 223 : JabRefGUI.directoryUpdateMonitor is not considered as Runnable during the call to HeadlessExecutorService.INSTANCE.executeInterruptableTask().

JabRefGUI.dialogService = new JabRefDialogService(mainStage);
Injector.setModelOrService(DialogService.class, dialogService);

DefaultDirectoryUpdateMonitor directoryUpdateMonitor = new DefaultDirectoryUpdateMonitor(new BibDatabaseContext(), preferences, fileUpdateMonitor, countingUndoManager, stateManager, dialogService, taskExecutor);
Copy link
Member

Choose a reason for hiding this comment

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

I'm haven't finished analyzing the code, but maybe you can hint how DefaultDirectoryUpdateMonitor understands to which database put the found entry? I think this will also help other reviewers

Copy link
Author

Choose a reason for hiding this comment

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

Honestly I don't know how it works neither, and it is not supposed to. I tried this because I needed a BibDatabaseContext to call the ImportHandler and add the PDFs in the watcher, but the BibDatabaseContext is not already initialized in JabRefGUI so this one is empty. When I tested the feature the newly created PDF in the mirrored structure seemed to be added to the right database for an unknown reason. I still need to figure out why.

Copy link
Author

Choose a reason for hiding this comment

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

I tested it today and it doesn't work anymore. At least that's the expected behaviour.

Copy link
Author

Choose a reason for hiding this comment

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

I think I fixed this in the commit n°669c37c : now the new entry should always be added to the database of the corresponding group. It also works if the user has multiple opened libraries.

dialogService.notify(Localization.lang("Added group \"%0\".", group.getName()));
if (group instanceof DirectoryGroup directoryStructureRoot) {
try {
writeGroupChangesToMetaData();
Copy link
Member

Choose a reason for hiding this comment

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

Maybe you can put writeGroupChangesToMetaData() before the if?

Copy link
Author

Choose a reason for hiding this comment

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

You're right I can delete the second call to writeGroupChangesToMetaData() thanks to that. I'll fix this in the next commit.


void directoryDeleted();

void PDFDeleted(Path PDFPath);
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit worried, that this follow pascal-case instead of camel-case

Copy link
Author

Choose a reason for hiding this comment

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

That's right, I'll fix it soon to follow camel-case instead.

@InAnYan
Copy link
Member

InAnYan commented Nov 13, 2025

Is this PR work in progress?

@ErwanGou
Copy link
Author

Is this PR work in progress?

Not really. The recent commits are minor changes to try fixing the issues raised by the jabref bot, but I don't understand why it is still not working. I also regularly update my branch. Apart from that the feature lacks some improvements as detailled in the 'Next steps' section, but I need your help for those.

@InAnYan
Copy link
Member

InAnYan commented Nov 13, 2025

But why there are TODO comments?

@ErwanGou
Copy link
Author

But why there are TODO comments?

The issue in its entirety is not resolved by the actual code that I propose so I detailled what is missing in the 'Next steps' section of the PR and to remember it I added some TODO comments that I can delete if you prefer. For now I'm a bit stuck on these missing features so I wanted some feedback about the existing code before continuing.

@InAnYan
Copy link
Member

InAnYan commented Nov 13, 2025

Ah, okay, you made this PR not draft, so we could give you feedback. Now I understand

CHANGELOG.md Outdated
- We added "IEEE" as another option for parsing plain text citations. [#14233](github.com/JabRef/jabref/pull/14233)
- We added automatic date-based groups that create year/month/day subgroups from an entry’s date fields. [#10822](https://github.com/JabRef/jabref/issues/10822)
- We added `doi-to-bibtex` to `JabKit`. [#14244](https://github.com/JabRef/jabref/pull/14244)
- We added groups that mirror the local structure. [#10930](https://github.com/JabRef/jabref/issues/10930)
Copy link
Member

Choose a reason for hiding this comment

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

Proposal to refinement, because "structure" alone is a very vague term.

Suggested change
- We added groups that mirror the local structure. [#10930](https://github.com/JabRef/jabref/issues/10930)
- We added groups that mirror the local file directory structure. [#10930](https://github.com/JabRef/jabref/issues/10930)

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for your review, I applied the change.

@jabref-machine
Copy link
Collaborator

You ticked that you modified CHANGELOG.md, but no new entry was found there.

If you made changes that are visible to the user, please add a brief description along with the issue number to the CHANGELOG.md file. If you did not, please replace the cross ([x]) by a slash ([/]) to indicate that no CHANGELOG.md entry is necessary. More details can be found in our Developer Documentation about the changelog.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dynamic group mirroring the file system.

5 participants