diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ceca044a89..f665c96f50e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,12 +17,15 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `# - Updated French translation - We improved the handling of abstracts in the "Astrophysics Data System" fetcher. [#2471](https://github.com/JabRef/jabref/issues/2471) - We added support for pasting entries in different formats [#3143](https://github.com/JabRef/jabref/issues/3143) +- In the annotation tab, PDF files are now monitored for new or changed annotation. A manual reload is no longer necessary. [#3292](https://github.com/JabRef/jabref/issues/3292) - We increased the relative size of the "abstract" field in the entry editor. [Feature request in the forum](http://discourse.jabref.org/t/entry-preview-in-version-4/827) - Crossreferenced entries are now used when a BibTex key is generated for an entry with empty fields. [#2811](https://github.com/JabRef/jabref/issues/2811) - We now set the WM_CLASS of the UI to org-jabref-JabRefMain to allow certain Un*x window managers to properly identify its windows - We changed the default paths for the OpenOffice/LibreOffice binaries to the default path for LibreOffice - We no longer create a new entry editor when selecting a new entry to increase performance. [#3187](https://github.com/JabRef/jabref/pull/3187) - We added the possibility to copy linked files from entries to a single output folder [#2539](https://github.com/JabRef/jabref/pull/2593) +- We increased performance and decreased the memory footprint of the entry editor drastically. [#3331](https://github.com/JabRef/jabref/pull/3331) + ### Fixed - We fixed the translation of \textendash in the entry preview [#3307](https://github.com/JabRef/jabref/issues/3307) diff --git a/src/main/java/org/jabref/Globals.java b/src/main/java/org/jabref/Globals.java index 27fa65c92a6..fb854526e0a 100644 --- a/src/main/java/org/jabref/Globals.java +++ b/src/main/java/org/jabref/Globals.java @@ -4,11 +4,11 @@ import java.util.Optional; import java.util.UUID; -import org.jabref.collab.FileUpdateMonitor; import org.jabref.gui.GlobalFocusListener; import org.jabref.gui.StateManager; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.ImportFormatReader; import org.jabref.logic.journals.JournalAbbreviationLoader; diff --git a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java index 84e21a156a2..0706dbdde9d 100644 --- a/src/main/java/org/jabref/collab/ChangeDisplayDialog.java +++ b/src/main/java/org/jabref/collab/ChangeDisplayDialog.java @@ -103,7 +103,7 @@ public ChangeDisplayDialog(JFrame owner, final BasePanel panel, if (anyDisabled) { panel.markBaseChanged(); } - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); dispose(); okPressed = true; }); diff --git a/src/main/java/org/jabref/collab/ChangeScanner.java b/src/main/java/org/jabref/collab/ChangeScanner.java index c413b583d67..855ce8afe9c 100644 --- a/src/main/java/org/jabref/collab/ChangeScanner.java +++ b/src/main/java/org/jabref/collab/ChangeScanner.java @@ -49,6 +49,7 @@ public class ChangeScanner implements Runnable { private static final double MATCH_THRESHOLD = 0.4; private final File file; + private final Path tempFile; private final BibDatabase databaseInMemory; private final MetaData metadataInMemory; @@ -67,12 +68,13 @@ public class ChangeScanner implements Runnable { // NamedCompound edit = new NamedCompound("Merged external changes") - public ChangeScanner(JabRefFrame frame, BasePanel bp, File file) { + public ChangeScanner(JabRefFrame frame, BasePanel bp, File file, Path tempFile) { this.panel = bp; this.frame = frame; this.databaseInMemory = bp.getDatabase(); this.metadataInMemory = bp.getBibDatabaseContext().getMetaData(); this.file = file; + this.tempFile = tempFile; } @Override @@ -80,7 +82,6 @@ public void run() { try { // Parse the temporary file. - Path tempFile = Globals.getFileUpdateMonitor().getTempFile(panel.fileMonitorHandle()); ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences(); ParserResult result = OpenDatabase.loadDatabase(tempFile.toFile(), importFormatPreferences); databaseInTemp = result.getDatabase(); @@ -153,7 +154,7 @@ private void storeTempDatabase() { Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode()); BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new); SaveSession ss = databaseWriter.saveDatabase(new BibDatabaseContext(databaseInTemp, metadataInTemp, defaults), prefs); - ss.commit(Globals.getFileUpdateMonitor().getTempFile(panel.fileMonitorHandle())); + ss.commit(tempFile); } catch (SaveException ex) { LOGGER.warn("Problem updating tmp file after accepting external changes", ex); } diff --git a/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java b/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java new file mode 100644 index 00000000000..bce1af7d10d --- /dev/null +++ b/src/main/java/org/jabref/collab/DatabaseChangeMonitor.java @@ -0,0 +1,161 @@ +package org.jabref.collab; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import javax.swing.SwingUtilities; + +import org.jabref.JabRefExecutorService; +import org.jabref.gui.BasePanel; +import org.jabref.gui.SidePaneManager; +import org.jabref.gui.util.FileUpdateListener; +import org.jabref.gui.util.FileUpdateMonitor; +import org.jabref.logic.util.io.FileBasedLock; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class DatabaseChangeMonitor implements FileUpdateListener { + private static final Log LOGGER = LogFactory.getLog(DatabaseChangeMonitor.class); + + private final BibDatabaseContext database; + private final FileUpdateMonitor fileMonitor; + private final BasePanel panel; + private boolean updatedExternally; + private Path tmpFile; + private long timeStamp; + private long fileSize; + + public DatabaseChangeMonitor(BibDatabaseContext database, FileUpdateMonitor fileMonitor, BasePanel panel) { + this.database = database; + this.fileMonitor = fileMonitor; + this.panel = panel; + + this.database.getDatabasePath().ifPresent(path -> { + try { + fileMonitor.addListenerForFile(path, this); + timeStamp = Files.getLastModifiedTime(path).toMillis(); + fileSize = Files.size(path); + tmpFile = Files.createTempFile("jabref", ".bib"); + tmpFile.toFile().deleteOnExit(); + copyToTemp(path); + } catch (IOException e) { + LOGGER.error("Error while trying to monitor " + path, e); + } + }); + } + + @Override + public void fileUpdated() { + if (panel.isSaving()) { + // We are just saving the file, so this message is most likely due to bad timing. + // If not, we'll handle it on the next polling. + return; + } + + updatedExternally = true; + + final ChangeScanner scanner = new ChangeScanner(panel.frame(), panel, database.getDatabaseFile().orElse(null), tmpFile); + + // Test: running scan automatically in background + if (database.getDatabasePath().isPresent() && !FileBasedLock.waitForFileLock(database.getDatabasePath().get())) { + // The file is locked even after the maximum wait. Do nothing. + LOGGER.error("File updated externally, but change scan failed because the file is locked."); + + // Wait a bit and then try again + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Nothing to do + } + fileUpdated(); + return; + } + + JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); + + // Adding the sidepane component is Swing work, so we must do this in the Swing + // thread: + Runnable t = () -> { + + // Check if there is already a notification about external + // changes: + SidePaneManager sidePaneManager = panel.getSidePaneManager(); + boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.class); + if (hasAlready) { + sidePaneManager.hideComponent(FileUpdatePanel.class); + sidePaneManager.unregisterComponent(FileUpdatePanel.class); + } + FileUpdatePanel pan = new FileUpdatePanel(panel, sidePaneManager, + database.getDatabaseFile().orElse(null), scanner); + sidePaneManager.register(pan); + sidePaneManager.show(FileUpdatePanel.class); + }; + + if (scanner.changesFound()) { + SwingUtilities.invokeLater(t); + } else { + updatedExternally = false; + } + } + + /** + * Forces a check on the file, and returns the result. Check if time stamp or the file size has changed. + * + * @return boolean true if the file has changed. + */ + private boolean hasBeenModified() { + Optional file = database.getDatabasePath(); + if (file.isPresent()) { + try { + long modified = Files.getLastModifiedTime(file.get()).toMillis(); + if (modified == 0L) { + // File deleted + return false; + } + long fileSizeNow = Files.size(file.get()); + return (timeStamp != modified) || (fileSize != fileSizeNow); + } catch (IOException ex) { + return false; + } + } + return false; + } + + public void unregister() { + database.getDatabasePath().ifPresent(file -> fileMonitor.removeListener(file, this)); + } + + public boolean hasBeenModifiedExternally() { + return updatedExternally || hasBeenModified(); + } + + public void markExternalChangesAsResolved() { + updatedExternally = false; + } + + public void markAsSaved() { + database.getDatabasePath().ifPresent(file -> { + try { + timeStamp = Files.getLastModifiedTime(file).toMillis(); + fileSize = Files.size(file); + + copyToTemp(file); + } catch (IOException ex) { + LOGGER.error("Error while getting file information", ex); + } + }); + } + + private void copyToTemp(Path file) { + FileUtil.copyFile(file, tmpFile, true); + } + + public Path getTempFile() { + return tmpFile; + } +} diff --git a/src/main/java/org/jabref/collab/FileUpdateMonitor.java b/src/main/java/org/jabref/collab/FileUpdateMonitor.java deleted file mode 100644 index 9d009f19848..00000000000 --- a/src/main/java/org/jabref/collab/FileUpdateMonitor.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.jabref.collab; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -import org.jabref.logic.util.io.FileUtil; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * This thread monitors a set of files, each associated with a FileUpdateListener, for changes - * in the file's last modification time stamp. The - */ -public class FileUpdateMonitor implements Runnable { - private static final Log LOGGER = LogFactory.getLog(FileUpdateMonitor.class); - - private static final int WAIT = 4000; - private final Map entries = new HashMap<>(); - private int numberOfUpdateListener; - - private static synchronized Path getTempFile() { - Path temporaryFile = null; - try { - temporaryFile = Files.createTempFile("jabref", null); - temporaryFile.toFile().deleteOnExit(); - } catch (IOException ex) { - LOGGER.warn("Could not create temporary file.", ex); - } - return temporaryFile; - } - - @Override - public void run() { - // The running variable is used to make the thread stop when needed. - while (true) { - for (Entry e : entries.values()) { - try { - if (e.hasBeenUpdated()) { - e.notifyListener(); - } - } catch (IOException ex) { - e.notifyFileRemoved(); - } - } - - // Sleep for a while before starting a new polling round. - try { - Thread.sleep(WAIT); - } catch (InterruptedException ex) { - LOGGER.debug("FileUpdateMonitor has been interrupted. Terminating...", ex); - return; - } - } - } - - /** - * Add a new file to monitor. Returns a handle for accessing the entry. - * @param ul FileUpdateListener The listener to notify when the file changes. - * @param file File The file to monitor. - * @throws IOException if the file does not exist. - */ - public String addUpdateListener(FileUpdateListener ul, File file) throws IOException { - if (!file.exists()) { - throw new IOException("File not found"); - } - numberOfUpdateListener++; - String key = String.valueOf(numberOfUpdateListener); - entries.put(key, new Entry(ul, file.toPath())); - return key; - } - - /** - * Forces a check on the file, and returns the result. Does not - * force a report to all listeners before the next routine check. - */ - public boolean hasBeenModified(String handle) { - Entry entry = entries.get(handle); - if (entry == null) { - return false; - } - try { - return entry.hasBeenUpdated(); - } catch (IOException ex) { - // Thrown if file has been removed. We return false. - return false; - } - } - - /** - * Change the stored timestamp for the given file. If the timestamp equals - * the file's timestamp on disk, after this call the file will appear to - * have been modified. Used if a file has been modified, and the change - * scan fails, in order to ensure successive checks. - * @param handle the handle to the correct file. - */ - public void perturbTimestamp(String handle) { - Entry entry = entries.get(handle); - if (entry != null) { - entry.decreaseTimeStamp(); - } - } - - /** - * Removes a listener from the monitor. - * @param handle String The handle for the listener to remove. - */ - public void removeUpdateListener(String handle) { - entries.remove(handle); - } - - public void updateTimeStamp(String key) { - Entry entry = entries.get(key); - if (entry != null) { - try { - entry.updateTimeStamp(); - } catch (IOException e) { - LOGGER.error("Couldn't update timestamp", e); - } - } - } - - /** - * Method for getting the temporary file used for this database. The tempfile - * is used for comparison with the changed on-disk version. - * @param key String The handle for this monitor. - * @throws IllegalArgumentException If the handle doesn't correspond to an entry. - * @return Path The temporary file. - */ - public Path getTempFile(String key) throws IllegalArgumentException { - Entry entry = entries.get(key); - if (entry == null) { - throw new IllegalArgumentException("Entry not found"); - } - return entry.getTmpFile(); - } - - /** - * A class containing the File, the FileUpdateListener and the current time stamp for one file. - */ - static class Entry { - - private final FileUpdateListener listener; - private final Path file; - private final Path tmpFile; - private long timeStamp; - private long fileSize; - - - public Entry(FileUpdateListener ul, Path f) throws IOException { - listener = ul; - file = f; - timeStamp = Files.getLastModifiedTime(file).toMillis(); - fileSize = Files.size(file); - tmpFile = FileUpdateMonitor.getTempFile(); - if (tmpFile != null) { - tmpFile.toFile().deleteOnExit(); - copy(); - } - } - - /** - * Check if time stamp or the file size has changed. - * @throws IOException if the file does no longer exist. - * @return boolean true if the file has changed. - */ - public boolean hasBeenUpdated() throws IOException { - long modified = Files.getLastModifiedTime(file).toMillis(); - if (modified == 0L) { - throw new IOException("File deleted"); - } - long fileSizeNow = Files.size(file); - return (timeStamp != modified) || (fileSize != fileSizeNow); - } - - public void updateTimeStamp() throws IOException { - timeStamp = Files.getLastModifiedTime(file).toMillis(); - if (timeStamp == 0L) { - notifyFileRemoved(); - } - fileSize = Files.size(file); - - copy(); - } - - public boolean copy() { - - return FileUtil.copyFile(file, tmpFile, true); - - } - - /** - * Call the listener method to signal that the file has changed. - */ - public void notifyListener() throws IOException { - // Update time stamp. - timeStamp = Files.getLastModifiedTime(file).toMillis(); - fileSize = Files.size(file); - listener.fileUpdated(); - } - - /** - * Call the listener method to signal that the file has been removed. - */ - public void notifyFileRemoved() { - listener.fileRemoved(); - } - - public Path getTmpFile() { - return tmpFile; - } - - public void decreaseTimeStamp() { - timeStamp--; - } - } -} diff --git a/src/main/java/org/jabref/collab/FileUpdatePanel.java b/src/main/java/org/jabref/collab/FileUpdatePanel.java index facbb7408d2..cd502a03edc 100644 --- a/src/main/java/org/jabref/collab/FileUpdatePanel.java +++ b/src/main/java/org/jabref/collab/FileUpdatePanel.java @@ -104,7 +104,7 @@ public void actionPerformed(ActionEvent e) { public void scanResultsResolved(boolean resolved) { if (resolved) { manager.hideComponent(this); - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); } } } diff --git a/src/main/java/org/jabref/gui/AbstractView.java b/src/main/java/org/jabref/gui/AbstractView.java index 33b8a1cc4ca..055d5a69d0e 100644 --- a/src/main/java/org/jabref/gui/AbstractView.java +++ b/src/main/java/org/jabref/gui/AbstractView.java @@ -32,8 +32,7 @@ public Parent getView() { // Notify controller about the stage, where it is displayed view.sceneProperty().addListener((observable, oldValue, newValue) -> { - - if (newValue.getWindow() instanceof Stage) { + if (newValue != null && newValue.getWindow() instanceof Stage) { Stage stage = (Stage) newValue.getWindow(); if (stage != null) { getController().ifPresent(controller -> controller.setStage(stage)); diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 692db1be4d1..1fcec7a9728 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -43,8 +43,7 @@ import org.jabref.Globals; import org.jabref.JabRefExecutorService; -import org.jabref.collab.ChangeScanner; -import org.jabref.collab.FileUpdateListener; +import org.jabref.collab.DatabaseChangeMonitor; import org.jabref.collab.FileUpdatePanel; import org.jabref.gui.actions.Actions; import org.jabref.gui.actions.BaseAction; @@ -116,7 +115,6 @@ import org.jabref.logic.search.SearchQuery; import org.jabref.logic.util.FileExtensions; import org.jabref.logic.util.UpdateField; -import org.jabref.logic.util.io.FileBasedLock; import org.jabref.logic.util.io.FileFinder; import org.jabref.logic.util.io.FileFinders; import org.jabref.logic.util.io.FileUtil; @@ -148,7 +146,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener { +public class BasePanel extends JPanel implements ClipboardOwner { private static final Log LOGGER = LogFactory.getLog(BasePanel.class); @@ -178,9 +176,8 @@ public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListe private EntryEditor currentEditor; private MainTableSelectionListener selectionListener; private JSplitPane splitPane; - private String fileMonitorHandle; private boolean saving; - private boolean updatedExternally; + // AutoCompleter used in the search bar private PersonNameSuggestionProvider searchAutoCompleter; private boolean baseChanged; @@ -201,6 +198,8 @@ public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListe // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); + private DatabaseChangeMonitor changeMonitor; + public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { Objects.requireNonNull(frame); Objects.requireNonNull(bibDatabaseContext); @@ -229,11 +228,7 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { Optional file = bibDatabaseContext.getDatabaseFile(); if (file.isPresent()) { // Register so we get notifications about outside changes to the file. - try { - fileMonitorHandle = Globals.getFileUpdateMonitor().addUpdateListener(this, file.get()); - } catch (IOException ex) { - LOGGER.warn("Could not register FileUpdateMonitor", ex); - } + changeMonitor = new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this); } else { if (bibDatabaseContext.getDatabase().hasEntries()) { // if the database is not empty and no file is assigned, @@ -1794,71 +1789,12 @@ private void setEntryEditorEnabled(boolean enabled) { } } - public String fileMonitorHandle() { - return fileMonitorHandle; - } - - @Override - public void fileUpdated() { - if (saving) { - // We are just saving the file, so this message is most likely due to bad timing. - // If not, we'll handle it on the next polling. - return; - } - - updatedExternally = true; - - final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this, - getBibDatabaseContext().getDatabaseFile().orElse(null)); - - // Test: running scan automatically in background - if ((getBibDatabaseContext().getDatabaseFile().isPresent()) - && !FileBasedLock.waitForFileLock(getBibDatabaseContext().getDatabaseFile().get().toPath())) { - // The file is locked even after the maximum wait. Do nothing. - LOGGER.error("File updated externally, but change scan failed because the file is locked."); - // Perturb the stored timestamp so successive checks are made: - Globals.getFileUpdateMonitor().perturbTimestamp(getFileMonitorHandle()); - return; - } - - JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); - - // Adding the sidepane component is Swing work, so we must do this in the Swing - // thread: - Runnable t = () -> { - - // Check if there is already a notification about external - // changes: - boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.class); - if (hasAlready) { - sidePaneManager.hideComponent(FileUpdatePanel.class); - sidePaneManager.unregisterComponent(FileUpdatePanel.class); - } - FileUpdatePanel pan = new FileUpdatePanel(BasePanel.this, sidePaneManager, - getBibDatabaseContext().getDatabaseFile().orElse(null), scanner); - sidePaneManager.register(pan); - sidePaneManager.show(FileUpdatePanel.class); - }; - - if (scanner.changesFound()) { - SwingUtilities.invokeLater(t); - } else { - setUpdatedExternally(false); - } - } - - @Override - public void fileRemoved() { - LOGGER.info("File '" + getBibDatabaseContext().getDatabaseFile().get().getPath() + "' has been deleted."); - } - /** * Perform necessary cleanup when this BasePanel is closed. */ public void cleanUp() { - if (fileMonitorHandle != null) { - Globals.getFileUpdateMonitor().removeUpdateListener(fileMonitorHandle); - } + changeMonitor.unregister(); + // Check if there is a FileUpdatePanel for this BasePanel being shown. If so, // remove it: if (sidePaneManager.hasComponent(FileUpdatePanel.class)) { @@ -1884,19 +1820,11 @@ public BibDatabaseContext getBibDatabaseContext() { } public boolean isUpdatedExternally() { - return updatedExternally; + return changeMonitor.hasBeenModifiedExternally(); } - public void setUpdatedExternally(boolean b) { - updatedExternally = b; - } - - public String getFileMonitorHandle() { - return fileMonitorHandle; - } - - public void setFileMonitorHandle(String fileMonitorHandle) { - this.fileMonitorHandle = fileMonitorHandle; + public void markExternalChangesAsResolved() { + changeMonitor.markExternalChangesAsResolved(); } public SidePaneManager getSidePaneManager() { @@ -2043,6 +1971,19 @@ public FileAnnotationCache getAnnotationCache() { return annotationCache; } + public void resetChangeMonitor() { + changeMonitor.unregister(); + changeMonitor = new DatabaseChangeMonitor(bibDatabaseContext, Globals.getFileUpdateMonitor(), this); + } + + public void updateTimeStamp() { + changeMonitor.markAsSaved(); + } + + public Path getTempFile() { + return changeMonitor.getTempFile(); + } + private static class SearchAndOpenFile { private final BibEntry entry; diff --git a/src/main/java/org/jabref/gui/DefaultInjector.java b/src/main/java/org/jabref/gui/DefaultInjector.java index 93845ce5dca..7e430afd0d2 100644 --- a/src/main/java/org/jabref/gui/DefaultInjector.java +++ b/src/main/java/org/jabref/gui/DefaultInjector.java @@ -4,6 +4,7 @@ import org.jabref.Globals; import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.preferences.PreferencesService; @@ -35,6 +36,8 @@ private static Object createDependency(Class clazz) { return Globals.journalAbbreviationLoader; } else if (clazz == StateManager.class) { return Globals.stateManager; + } else if (clazz == FileUpdateMonitor.class) { + return Globals.getFileUpdateMonitor(); } else { try { return clazz.newInstance(); diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 394e539a41a..1b9b5581a3c 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -39,19 +39,21 @@ */ class FieldsEditorTab extends EntryEditorTab { - private final Region panel; private final List fields; private final EntryEditor parent; private final Map editors = new LinkedHashMap<>(); private final JabRefFrame frame; private final BasePanel basePanel; private final BibEntry entry; + private final boolean isCompressed; + private Region panel; private FieldEditorFX activeField; + public FieldsEditorTab(JabRefFrame frame, BasePanel basePanel, List fields, EntryEditor parent, boolean addKeyField, boolean compressed, BibEntry entry) { this.entry = Objects.requireNonNull(entry); this.fields = new ArrayList<>(Objects.requireNonNull(fields)); - + this.isCompressed = compressed; // Add the edit field for Bibtex-key. if (addKeyField) { this.fields.add(BibEntry.KEY_FIELD); @@ -61,8 +63,6 @@ public FieldsEditorTab(JabRefFrame frame, BasePanel basePanel, List fiel this.frame = frame; this.basePanel = basePanel; - panel = setupPanel(frame, basePanel, compressed); - // The following line makes sure focus cycles inside tab instead of being lost to other parts of the frame: //panel.setFocusCycleRoot(true); } @@ -305,6 +305,7 @@ public void requestFocus() { @Override protected void initialize() { + panel = setupPanel(frame, basePanel, isCompressed); setContent(panel); } } diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml index 7f6abc4d52f..d0891792fc4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTab.fxml @@ -29,14 +29,6 @@
diff --git a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java index d29f6677239..6eb90070e11 100644 --- a/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java +++ b/src/main/java/org/jabref/gui/entryeditor/fileannotationtab/FileAnnotationTabController.java @@ -1,5 +1,7 @@ package org.jabref.gui.entryeditor.fileannotationtab; +import java.nio.file.Path; + import javax.inject.Inject; import javafx.beans.binding.Bindings; @@ -17,6 +19,7 @@ import javafx.scene.text.Text; import org.jabref.gui.AbstractController; +import org.jabref.gui.util.FileUpdateMonitor; import org.jabref.gui.util.ViewModelListCellFactory; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; @@ -27,7 +30,7 @@ public class FileAnnotationTabController extends AbstractController { @FXML - public ComboBox files; + public ComboBox files; @FXML public ListView annotationList; @FXML @@ -45,10 +48,12 @@ public class FileAnnotationTabController extends AbstractController annotations = new SimpleListProperty<>(FXCollections.observableArrayList()); - private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty files = new SimpleListProperty<>(FXCollections.observableArrayList()); private final ObjectProperty currentAnnotation = new SimpleObjectProperty<>(); private final FileAnnotationCache cache; private final BibEntry entry; - private Map> fileAnnotations; + private Map> fileAnnotations; + private Path currentFile; + private FileUpdateMonitor fileMonitor; + private FileUpdateListener fileListener = this::reloadAnnotations; - public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry) { + public FileAnnotationTabViewModel(FileAnnotationCache cache, BibEntry entry, FileUpdateMonitor fileMonitor) { this.cache = cache; this.entry = entry; + this.fileMonitor = fileMonitor; initialize(); } @@ -50,7 +63,7 @@ public ListProperty annotationsProperty() { return annotations; } - public ListProperty filesProperty() { + public ListProperty filesProperty() { return files; } @@ -58,21 +71,37 @@ public void notifyNewSelectedAnnotation(FileAnnotationViewModel newAnnotation) { currentAnnotation.set(newAnnotation); } - public void notifyNewSelectedFile(String newFile) { + public void notifyNewSelectedFile(Path newFile) { + fileMonitor.removeListener(currentFile, fileListener); + currentFile = newFile; + Comparator byPage = Comparator.comparingInt(FileAnnotation::getPage); - List newAnnotations = fileAnnotations.getOrDefault(newFile, new ArrayList<>()) + List newAnnotations = fileAnnotations.getOrDefault(currentFile, new ArrayList<>()) .stream() .filter(annotation -> (null != annotation.getContent())) .sorted(byPage) .map(FileAnnotationViewModel::new) .collect(Collectors.toList()); annotations.setAll(newAnnotations); + + try { + fileMonitor.addListenerForFile(currentFile, fileListener); + } catch (IOException e) { + LOGGER.error("Problem while watching file for changes " + currentFile, e); + } } - public void reloadAnnotations() { - cache.remove(entry); - initialize(); + private void reloadAnnotations() { + // Make sure to always run this in the JavaFX thread as the file monitor (and its notifications) live in a different thread + DefaultTaskExecutor.runInJavaFXThread(() -> { + // Remove annotations for the current entry and reinitialize annotation/cache + cache.remove(entry); + initialize(); + + // Pretend that we just switched to the current file in order to refresh the display + notifyNewSelectedFile(currentFile); + }); } /** diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index f0bb87f64f2..f54bb076a99 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -1,7 +1,6 @@ package org.jabref.gui.exporter; import java.io.File; -import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; @@ -145,7 +144,7 @@ public void run() { panel.getBibDatabaseContext().getMetaData().getEncoding() .orElse(Globals.prefs.getDefaultEncoding())); - Globals.getFileUpdateMonitor().updateTimeStamp(panel.getFileMonitorHandle()); + panel.updateTimeStamp(); } else { success = false; fileLockedError = true; @@ -161,7 +160,7 @@ public void run() { // since last save: panel.setNonUndoableChange(false); panel.setBaseChanged(false); - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); } } catch (SaveException ex) { if (ex == SaveException.FILE_LOCKED) { @@ -346,14 +345,7 @@ public void saveAs(File file) throws Exception { LOGGER.info("Old file not found, just creating a new file"); } context.setDatabaseFile(file); - - // Register so we get notifications about outside changes to the file. - try { - panel.setFileMonitorHandle(Globals.getFileUpdateMonitor().addUpdateListener(panel, - context.getDatabaseFile().orElse(null))); - } catch (IOException ex) { - LOGGER.error("Problem registering file change notifications", ex); - } + panel.resetChangeMonitor(); if (readyForAutosave(context)) { AutosaveManager autosaver = AutosaveManager.start(context); @@ -408,8 +400,7 @@ public boolean isCanceled() { */ private boolean checkExternalModification() { // Check for external modifications: - if (panel.isUpdatedExternally() - || Globals.getFileUpdateMonitor().hasBeenModified(panel.getFileMonitorHandle())) { + if (panel.isUpdatedExternally()) { String[] opts = new String[] {Localization.lang("Review changes"), Localization.lang("Save"), Localization.lang("Cancel")}; int answer = JOptionPane.showOptionDialog(panel.frame(), @@ -432,12 +423,12 @@ private boolean checkExternalModification() { } ChangeScanner scanner = new ChangeScanner(panel.frame(), panel, - panel.getBibDatabaseContext().getDatabaseFile().get()); + panel.getBibDatabaseContext().getDatabaseFile().get(), panel.getTempFile()); JabRefExecutorService.INSTANCE.executeInterruptableTaskAndWait(scanner); if (scanner.changesFound()) { scanner.displayResult(resolved -> { if (resolved) { - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); SwingUtilities .invokeLater(() -> panel.getSidePaneManager().hide(FileUpdatePanel.class)); } else { @@ -456,7 +447,7 @@ private boolean checkExternalModification() { Localization.lang("Protected library"), JOptionPane.ERROR_MESSAGE); canceled = true; } else { - panel.setUpdatedExternally(false); + panel.markExternalChangesAsResolved(); panel.getSidePaneManager().hide(FileUpdatePanel.class); } } diff --git a/src/main/java/org/jabref/collab/FileUpdateListener.java b/src/main/java/org/jabref/gui/util/FileUpdateListener.java similarity index 63% rename from src/main/java/org/jabref/collab/FileUpdateListener.java rename to src/main/java/org/jabref/gui/util/FileUpdateListener.java index 6e7b4024ce2..9941fc28816 100644 --- a/src/main/java/org/jabref/collab/FileUpdateListener.java +++ b/src/main/java/org/jabref/gui/util/FileUpdateListener.java @@ -1,4 +1,4 @@ -package org.jabref.collab; +package org.jabref.gui.util; public interface FileUpdateListener { @@ -6,10 +6,4 @@ public interface FileUpdateListener { * The file has been updated. A new call will not result until the file has been modified again. */ void fileUpdated(); - - /** - * The file does no longer exist. - */ - void fileRemoved(); - } diff --git a/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java b/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java new file mode 100644 index 00000000000..57f59a0e563 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/FileUpdateMonitor.java @@ -0,0 +1,88 @@ +package org.jabref.gui.util; + +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class monitors a set of files for changes. Upon detecting a change it notifies the registered {@link + * FileUpdateListener}s. + * + * Implementation based on https://stackoverflow.com/questions/16251273/can-i-watch-for-single-file-change-with-watchservice-not-the-whole-directory + */ +public class FileUpdateMonitor implements Runnable { + private static final Log LOGGER = LogFactory.getLog(FileUpdateMonitor.class); + + private final Multimap listeners = ArrayListMultimap.create(20, 4); + private WatchService watcher; + + @Override + public void run() { + try (WatchService watcher = FileSystems.getDefault().newWatchService()) { + this.watcher = watcher; + while (true) { + WatchKey key; + try { + key = watcher.take(); + } catch (InterruptedException e) { + return; + } + + for (WatchEvent event : key.pollEvents()) { + WatchEvent.Kind kind = event.kind(); + + if (kind == StandardWatchEventKinds.OVERFLOW) { + Thread.yield(); + continue; + } else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { + // We only handle "ENTRY_MODIFY" here, so the context is always a Path + @SuppressWarnings("unchecked") + WatchEvent ev = (WatchEvent) event; + Path path = ((Path) key.watchable()).resolve(ev.context()); + notifyAboutChange(path); + } + key.reset(); + } + Thread.yield(); + } + } catch (Throwable e) { + LOGGER.debug("FileUpdateMonitor has been interrupted. Terminating...", e); + } + } + + private void notifyAboutChange(Path path) { + listeners.get(path).forEach(FileUpdateListener::fileUpdated); + } + + /** + * Add a new file to monitor. + * + * @param file The file to monitor. + * @throws IOException if the file does not exist. + */ + public void addListenerForFile(Path file, FileUpdateListener listener) throws IOException { + // We can't watch files directly, so monitor their parent directory for updates + Path directory = file.getParent(); + directory.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY); + + listeners.put(file, listener); + } + + /** + * Removes a listener from the monitor. + * + * @param path The path to remove. + */ + public void removeListener(Path path, FileUpdateListener listener) { + listeners.remove(path, listener); + } +} diff --git a/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java index fffcf282a84..b2e8c1557bc 100644 --- a/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java +++ b/src/main/java/org/jabref/logic/pdf/EntryAnnotationImporter.java @@ -1,5 +1,6 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,14 +44,14 @@ private List getFilteredFileList() { * @param databaseContext The context is needed for the importer. * @return Map from each PDF to a list of file annotations */ - public Map> importAnnotationsFromFiles(BibDatabaseContext databaseContext) { - Map> annotations = new HashMap<>(); + public Map> importAnnotationsFromFiles(BibDatabaseContext databaseContext) { + Map> annotations = new HashMap<>(); AnnotationImporter importer = new PdfAnnotationImporter(); //import annotationsOfFiles if the selected files are valid which is checked in getFilteredFileList() for (LinkedFile linkedFile : this.getFilteredFileList()) { linkedFile.findIn(databaseContext, JabRefPreferences.getInstance().getFileDirectoryPreferences()) - .ifPresent(file -> annotations.put(file.getFileName().toString(), importer.importAnnotations(file))); + .ifPresent(file -> annotations.put(file, importer.importAnnotations(file))); } return annotations; } diff --git a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java index 3f465a170fb..e4b2796aa8a 100644 --- a/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java +++ b/src/main/java/org/jabref/logic/pdf/FileAnnotationCache.java @@ -1,5 +1,6 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -20,7 +21,7 @@ public class FileAnnotationCache { private final static int CACHE_SIZE = 10; //the inner list holds the annotations per file, the outer collection maps this to a BibEntry. - private LoadingCache>> annotationCache; + private LoadingCache>> annotationCache; /** * Creates an empty fil annotation cache. Required to allow the annotation cache to be injected into views without @@ -31,9 +32,9 @@ public FileAnnotationCache() { } public FileAnnotationCache(BibDatabaseContext context) { - annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { + annotationCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader>>() { @Override - public Map> load(BibEntry entry) throws Exception { + public Map> load(BibEntry entry) throws Exception { return new EntryAnnotationImporter(entry).importAnnotationsFromFiles(context); } }); @@ -45,7 +46,7 @@ public Map> load(BibEntry entry) throws Exception { * @param entry entry for which to get the annotations * @return Map containing a list of annotations in a list for each file */ - public Map> getFromCache(BibEntry entry) { + public Map> getFromCache(BibEntry entry) { LOGGER.debug(String.format("Loading Bibentry '%s' from cache.", entry.getCiteKeyOptional().orElse(entry.getId()))); return annotationCache.getUnchecked(entry); } diff --git a/src/main/resources/l10n/JabRef_da.properties b/src/main/resources/l10n/JabRef_da.properties index d20e311f660..daf35859098 100644 --- a/src/main/resources/l10n/JabRef_da.properties +++ b/src/main/resources/l10n/JabRef_da.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF-filen_findes_ikke File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Import_fra_ren_tekst Please_enter_a_name_for_the_group.=Skriv_et_navn_til_gruppen. diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 303dd1ece25..d359de55b60 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_existiert_nicht File_has_no_attached_annotations=Datei_hat_keine_angefügten_Annotationen -Reload_annotations=Annotationen_erneut_laden - Plain_text_import=Klartext_importieren Please_enter_a_name_for_the_group.=Bitte_geben_Sie_einen_Namen_für_die_Gruppe_ein. diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 4681cbffbbf..f6715a61e0c 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.= diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 9a2c38e599b..5dcea37328b 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_does_not_exist File_has_no_attached_annotations=File_has_no_attached_annotations -Reload_annotations=Reload_annotations - Plain_text_import=Plain_text_import Please_enter_a_name_for_the_group.=Please_enter_a_name_for_the_group. diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index f7245aa96fd..f8c99ead660 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=No_existe_el_PDF File_has_no_attached_annotations=El_fichero_no_tiene_anotaciones_adjuntas -Reload_annotations=Recargar_anotaciones - Plain_text_import=Importar_texto_plano Please_enter_a_name_for_the_group.=Introduzca_un_nombre_para_el_grupo. diff --git a/src/main/resources/l10n/JabRef_fa.properties b/src/main/resources/l10n/JabRef_fa.properties index be6d507b071..bdb8b6dc818 100644 --- a/src/main/resources/l10n/JabRef_fa.properties +++ b/src/main/resources/l10n/JabRef_fa.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.= diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index ef5415e4342..034227f2e4c 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=Le_PDF_n'existe_pas File_has_no_attached_annotations=Le_fichier_n'a_pas_d'annotations_liées -Reload_annotations=Recharger_les_annotations - Plain_text_import=Importation_de_texte_brut Please_enter_a_name_for_the_group.=SVP,_entrez_un_nom_pour_le_groupe. diff --git a/src/main/resources/l10n/JabRef_in.properties b/src/main/resources/l10n/JabRef_in.properties index 1f52468cecd..eb1d30aaf9f 100644 --- a/src/main/resources/l10n/JabRef_in.properties +++ b/src/main/resources/l10n/JabRef_in.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_tidak_ada File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Impor_teks_normal Please_enter_a_name_for_the_group.=Tuliskan_nama_untuk_grup. diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 204a75e7817..ae31398d186 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=Il_file_PDF_non_esiste File_has_no_attached_annotations=Il_file_non_ha_annotazioni_allegate -Reload_annotations=Ricarica_le_annotazioni - Plain_text_import=Importazione_da_solo_testo Please_enter_a_name_for_the_group.=Immettere_un_nome_per_il_gruppo diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 88406825a39..2ad735a27b2 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDFが存在しません File_has_no_attached_annotations=ファイルには註釈が付けられていません -Reload_annotations=註釈を再度読み込み - Plain_text_import=平文読み込み Please_enter_a_name_for_the_group.=このグループ用の名称を入力してください. diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 50380ad29ff..e8ab686bf3c 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -899,8 +899,6 @@ PDF_does_not_exist= File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Onopgemaakte_tekst_importeren Please_enter_a_name_for_the_group.=Geef_a.u.b._een_naam_voor_de_groep. diff --git a/src/main/resources/l10n/JabRef_no.properties b/src/main/resources/l10n/JabRef_no.properties index 58509661cb4..60f07443f89 100644 --- a/src/main/resources/l10n/JabRef_no.properties +++ b/src/main/resources/l10n/JabRef_no.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF-filen_finnes_ikke File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Import_fra_ren_tekst Please_enter_a_name_for_the_group.=Skriv_inn_et_navn_for_gruppen. diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 2f2639701d0..bec9b21e8c8 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=O_PDF_não_existe File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Importação_de_texto_puro Please_enter_a_name_for_the_group.=Por_favor,_digite_um_nome_para_o_grupo. diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 981a8192754..950b0f68f9c 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_не_существует File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Импорт_неформатированного_текста Please_enter_a_name_for_the_group.=Введите_имя_для_группы. diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index bbaa280c58f..715d5f0d471 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_finns_inte File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import= Please_enter_a_name_for_the_group.=Ange_ett_namn_för_gruppen. diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index 9562d1efb4f..641f10d8b81 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_mevcut_değil File_has_no_attached_annotations=Dosyanın_eklenmiş_açıklama_notu_yok -Reload_annotations=Açıklama_notlarını_yeniden_yükle - Plain_text_import=Düz_metin_içe_aktarma Please_enter_a_name_for_the_group.=Lütfen_grup_için_bir_isim_giriniz. diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 050b8c86479..e3e5d001f32 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_không_tồn_tại File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=Nhập_văn_bản_trơn Please_enter_a_name_for_the_group.=Vui_lòng_nhập_tên_cho_nhóm. diff --git a/src/main/resources/l10n/JabRef_zh.properties b/src/main/resources/l10n/JabRef_zh.properties index f625e1f7dc9..8abe6f070b5 100644 --- a/src/main/resources/l10n/JabRef_zh.properties +++ b/src/main/resources/l10n/JabRef_zh.properties @@ -899,8 +899,6 @@ PDF_does_not_exist=PDF_不存在 File_has_no_attached_annotations= -Reload_annotations= - Plain_text_import=纯文本导入 Please_enter_a_name_for_the_group.=请为该分组输入一个名字 diff --git a/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java b/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java index 70c0015277d..99689673e30 100644 --- a/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java +++ b/src/test/java/org/jabref/logic/pdf/EntryAnnotationImporterTest.java @@ -1,6 +1,7 @@ package org.jabref.logic.pdf; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; @@ -36,7 +37,7 @@ public void readEntryExampleThesis() { EntryAnnotationImporter entryAnnotationImporter = new EntryAnnotationImporter(entry); //when - Map> annotations = entryAnnotationImporter.importAnnotationsFromFiles(databaseContext); + Map> annotations = entryAnnotationImporter.importAnnotationsFromFiles(databaseContext); //then int fileCounter = 0;