Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7120843
Sync open directory libraries with external file changes (inbound)
koppor Jul 13, 2026
597658e
Link CHANGELOG entry to its pull request
koppor Jul 13, 2026
fec8496
Make org.apache.commons.io transitive in jablib
koppor Jul 13, 2026
53dc065
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
5e27ddc
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
f2484b7
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
677f056
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
567708b
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
1354d6d
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
1f65613
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 14, 2026
7fdc7e7
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Jul 16, 2026
e58d141
Watch Markdown sidecars for inbound synchronization
koppor Jul 16, 2026
77efd0c
Merge open-directory-library into directory-inbound-sync
koppor Sep 3, 2026
983cdb5
Harden the inbound directory synchronization
koppor Sep 3, 2026
93f9a60
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 3, 2026
2ac9dcc
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 3, 2026
ec87a3c
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 3, 2026
084c0cc
Apply IntelliJ formatter after upstream resync
koppor Sep 6, 2026
82c70e8
Merge open-directory-library into directory-inbound-sync
koppor Sep 7, 2026
ae191b0
Renumber the directory-as-library ADR to 0072
koppor Sep 7, 2026
d6d30df
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 7, 2026
e076855
Merge open-directory-library (with origin/main) into directory-inboun…
koppor Sep 12, 2026
775a2c2
Merge open-directory-library into directory-inbound-sync
koppor Sep 13, 2026
5c38d2a
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 13, 2026
fdf4fac
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 13, 2026
8fd8882
Merge branch 'open-directory-library' into directory-inbound-sync
koppor Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- Directory libraries now stay in sync with external file changes: creating, editing, deleting, or renaming `.yml`/`.md`/`.pdf` files in the opened folder updates the open library live, and renames keep the affected entries (selection and undo history survive). [#738](https://github.com/JabRef/jabref-koppor/pull/738)
- We added "Open folder as library" (File menu): a folder of PDFs and Hayagriva sidecar files (`.yml`, or `.md` notes with a Hayagriva frontmatter) opens as a library, and it is reopened on the next start. PDFs without a sidecar appear right away and get their metadata extracted in the background. Edits are not yet written back to the files. [#737](https://github.com/JabRef/jabref-koppor/pull/737)
- We added `jabkit git merge-driver`, a Git merge driver that merges `.bib` files semantically. [#16838](https://github.com/JabRef/jabref/pull/16838)

Expand Down
13 changes: 13 additions & 0 deletions docs/requirements/directory-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,17 @@ the last-opened list and routed back through the directory-library opener.

Needs: impl

## External file changes appear live in an open directory library
`req~directory-library.inbound-sync~2`

While a directory library is open, external creation, modification, deletion, and renaming of
`.yml`/`.yaml`/`.md`/`.pdf` files under its root must be reflected in the open library. Changed
entries are updated in place (the entry identity is preserved), renames are detected via a
grace window over the monitor's delete + create events and keep the affected entries, and files
written by JabRef itself are recognized by fingerprint and not re-imported. All resulting
database mutations carry a non-local event source so the future write-back direction can ignore
them.

Needs: impl

<!-- markdownlint-disable-file MD022 -->
2 changes: 1 addition & 1 deletion jabgui/src/main/java/org/jabref/gui/JabRefGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.jabref.gui.remote.CLIMessageHandler;
import org.jabref.gui.theme.ThemeManager;
import org.jabref.gui.util.DefaultFileUpdateMonitor;
import org.jabref.gui.util.DirectoryMonitor;
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.gui.walkthrough.WalkthroughPane;
import org.jabref.http.manager.HttpServerManager;
Expand All @@ -55,6 +54,7 @@
import org.jabref.logic.search.sqlbased.IndexManager;
import org.jabref.logic.search.sqlbased.PostgresServer;
import org.jabref.logic.util.BuildInfo;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.logic.util.FallbackExceptionHandler;
import org.jabref.logic.util.HeadlessExecutorService;
import org.jabref.logic.util.TaskExecutor;
Expand Down
4 changes: 4 additions & 0 deletions jabgui/src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,10 @@ private void onClosed(Event event) {
if (dataLoadingTask != null) {
dataLoadingTask.cancel();
}
if (bibDatabaseContext.getLocation() == DatabaseLocation.DIRECTORY) {
// Stops the directory watcher and shuts the synchronizer down
bibDatabaseContext.convertToLocalDatabase();
}
if (bibDatabaseContext.getLocation() == DatabaseLocation.SHARED) {
closeSharedDatabase(bibDatabaseContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@
import org.jabref.gui.undo.RedoAction;
import org.jabref.gui.undo.UndoAction;
import org.jabref.gui.util.BaseDialog;
import org.jabref.gui.util.DirectoryMonitor;
import org.jabref.gui.util.DragDrop;
import org.jabref.logic.ai.AiService;
import org.jabref.logic.citation.SearchCitationsRelationsService;
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.importer.EntryBasedFetcher;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.util.BuildInfo;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import org.jabref.gui.preview.PreviewPanel;
import org.jabref.gui.undo.RedoAction;
import org.jabref.gui.undo.UndoAction;
import org.jabref.gui.util.DirectoryMonitor;
import org.jabref.logic.citation.SearchCitationsRelationsService;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.util.BuildInfo;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.util.FileUpdateMonitor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import org.jabref.gui.icon.IconTheme;
import org.jabref.gui.preferences.GuiPreferences;
import org.jabref.gui.texparser.CitationsDisplay;
import org.jabref.gui.util.DirectoryMonitor;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.model.entry.BibEntry;

import com.tobiasdiez.easybind.EasyBind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
import org.jabref.gui.push.GuiPushToTeXstudio;
import org.jabref.gui.texparser.CitationsDisplay;
import org.jabref.gui.util.DirectoryDialogConfiguration;
import org.jabref.gui.util.DirectoryMonitor;
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.texparser.DefaultLatexParser;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.logic.ai.AiService;
import org.jabref.logic.directorylibrary.DirectoryLibraryScanner;
import org.jabref.logic.directorylibrary.DirectoryLibrarySynchronizer;
import org.jabref.logic.directorylibrary.PdfEnrichmentTask;
import org.jabref.logic.directorylibrary.PdfEntryFactory;
import org.jabref.logic.git.util.GitHandlerRegistry;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackgroundTask;
import org.jabref.logic.util.DirectoryMonitor;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.util.FileUpdateMonitor;

import com.airhacks.afterburner.injection.Injector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -121,9 +125,18 @@ private void showLibraryTab(DirectoryLibraryScanner.ScanResult scanResult, PdfEn
taskExecutor,
gitHandlerRegistry);
tabContainer.addTab(libraryTab, true);
// No change event follows the synchronous tab creation, so set the initial title here
libraryTab.updateTabTitle(false);

BibDatabaseContext databaseContext = scanResult.databaseContext();
DirectoryLibrarySynchronizer synchronizer = new DirectoryLibrarySynchronizer(
databaseContext, scanResult.catalog(), pdfEntryFactory, UiTaskExecutor::runInJavaFXThread);
databaseContext.attachDirectorySynchronizer(synchronizer);
synchronizer.startWatching(Injector.instantiateModelOrService(DirectoryMonitor.class));

if (!scanResult.pendingPdfImports().isEmpty()) {
PdfEnrichmentTask enrichment = new PdfEnrichmentTask(scanResult.pendingPdfImports(), pdfEntryFactory,
scanResult.databaseContext(), UiTaskExecutor::runInJavaFXThread);
databaseContext, UiTaskExecutor::runInJavaFXThread);
enrichment.onFailure(exception -> LOGGER.error("Extracting PDF metadata failed", exception));
cancelOnClose(libraryTab, enrichment);
enrichment.executeWith(taskExecutor);
Expand Down
2 changes: 1 addition & 1 deletion jablib/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@
requires transitive com.google.common;
requires java.string.similarity;
requires transitive org.apache.commons.csv;
requires org.apache.commons.io;
requires transitive org.apache.commons.io;
requires org.apache.commons.lang3;
requires org.apache.commons.text;
// endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,18 @@ public Optional<EntrySource> sourceOf(BibEntry entry) {
public List<String> entryIdsIn(Path yamlFile) {
return List.copyOf(entryIdsByFile.getOrDefault(yamlFile, List.of()));
}

/// Re-homes all entries of `oldFile` to `newFile` (a rename/move on disk).
public void relocateFile(Path oldFile, Path newFile) {
Optional.ofNullable(entryIdsByFile.remove(oldFile)).ifPresent(entryIds -> {
entryIdsByFile.put(newFile, entryIds);
entryIds.forEach(entryId -> sourceByEntryId.computeIfPresent(entryId,
(_, source) -> new EntrySource(newFile, source.hayagrivaKey())));
});
}

/// Forgets all entries of the given file (deleted on disk or re-registered afterwards).
public void removeFile(Path yamlFile) {
Optional.ofNullable(entryIdsByFile.remove(yamlFile)).ifPresent(entryIds -> entryIds.forEach(sourceByEntryId::remove));
}
}
Loading
Loading