-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add FileMonitor for LaTeX citations #10937
Add FileMonitor for LaTeX citations #10937
Conversation
…to LatexCitation (JabRef#10585) Known Issues: Sometimes throws ConcurrentModificationException from DefaultFileUpdateMonitor whenever a change occurs in the tracked directory Does not have any kind of shutdown Is not able to track changes in nested directories Does not handle whenever a directory is changed from the initial one properly Makes use of a Deprecated Class
…Ref#10585) Changed the MultiMap into a synchronized type for safety Used the Platform.runLater() to ensure processes are executed on correct threads Not sure what the side effects of these fixes are
Made sure that filemonitor first is added when the user navigates to LaTeX Citation for the first time for each .bib tab, also added logic to remove the old listener whenever the directory target is changed However, an issue is that listener seemingly is still watching over the old directory even though the listener has been removed
Known issues:
|
src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java
Outdated
Show resolved
Hide resolved
Hi, thanks for your interest in JabRef and OpenSource. Thank you especially for creating a PR early, so we can read you changes early and comment, if we see something going in a wrong direction. Which may be the case here. I spotted a small problem in your changes to DefaultFileUpdateMonitor. A solution that comes to mind spontaneously may be to inject a runner into the method, which may be in the ui thread? |
…ernative solution to thread issues (JabRef#10585)
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
also changed to use LOGGER.error instead of throw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small comments.
Did you try it out? I checked org.jabref.gui.util.DefaultFileUpdateMonitor#addListenerForFile
, which is code for a file not a directory.
The idea of the issue was that
- all .tex files are collected
- a watcher for each tex file is instantiated
Optionally: In case a user adds a new file to the directory, that file is monitored.
Implementation hint:
Class org.jabref.logic.texparser.DefaultLatexParser
needs to be modified to know the found latex citation keys per latex file. In case an "udpate" of the informaiton is triggered (by the file update monitor!!), all existing keys should be dropped and readded. Thus, org.jabref.model.texparser.LatexParserResult
also needs to adapted.
Test cases need to be written.
@@ -69,6 +74,9 @@ public LatexCitationsTabViewModel(BibDatabaseContext databaseContext, | |||
this.citationList = FXCollections.observableArrayList(); | |||
this.status = new SimpleObjectProperty<>(Status.IN_PROGRESS); | |||
this.searchError = new SimpleStringProperty(""); | |||
|
|||
this.fileUpdateMonitor = Globals.getFileUpdateMonitor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a comment. I tried to think whether com.airhacks.afterburner.injection.Injector#instantiateModelOrService(java.lang.Class<T>)
can be used. But it cannot be used, because it needs an empty constructor... Much more work at LatexCitationsTabViewModel
required.
Background: The FileUpdateMonitor should not be retrieved by globals, but via the injector. One would need in org.jabref.gui.entryeditor.LatexCitationsTab#LatexCitationsTab
at the first line (where the ViewModel is instanitated) to use com.airhacks.afterburner.injection.Injector#instantiateModelOrService(java.lang.Class<T>)
.
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java
Outdated
Show resolved
Hide resolved
Known Issues: When a directory is changed from initial one, the listerners stay on the initial one and aren't removed properly If a nested directory is removed, a listener continues to exist on an inexistant directory Makes use of a Deprecated Class
My previous commit failed the OpenRewrite job, I changed a string parameter in the listenerOnDirectory private method to an enum, which should make this job pass. Moreover, Files.walk is now within a try-catch block which removes a warning
…iggered (JabRef#10585) I created a Map<Path, FileUpdateListener> so that when a Listener is added to a path, you can recover it if you latter when to remove it. Before, the call to fileUpdateMonitor.removeListener didn’t actually removed the listener because we passed a FileUpdateListener that didn’t match the expected one. Moreover I created the function addListenerForDirectory, which internally calls addListenerForFile(directory, listener), because in this case A File and a Directory can be treated the same way. The only purpose of this function is for clarity, it didn’t really make sense to call addListenerForFile with a path representing a directory and not a file as parameter.
* @param directory The directory to monitor. | ||
* @throws IOException if the directory does not exist. | ||
*/ | ||
public void addListenerForDirectory(Path directory, FileUpdateListener listener) throws IOException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the method also to the interface so that there is no need to cast in LatexCitationsViewModel
// This function was created because it makes more sense to call addListenerForDirectory when | ||
// the Path is a directory and not a file, even though it does the same thing as addListenerForFile. | ||
// The function addListenerForFile works for directories as well. We have to listen to the parent | ||
// directory in this case too otherwise if a new file is created in the child directory, no |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. You really need to update the DefaultFileMonitorImplementation.
Here a test case that notifications for new files works:
diff --git a/src/main/java/org/jabref/gui/JabRefExecutorService.java b/src/main/java/org/jabref/gui/JabRefExecutorService.java
index 90c3b31bda..74eb0bbea5 100644
--- a/src/main/java/org/jabref/gui/JabRefExecutorService.java
+++ b/src/main/java/org/jabref/gui/JabRefExecutorService.java
@@ -184,12 +184,12 @@ public class JabRefExecutorService {
try {
// This is non-blocking. See https://stackoverflow.com/a/57383461/873282.
executorService.shutdown();
- if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
- LOGGER.debug("One minute passed, {} still not completed. Trying forced shutdown.", executorService.toString());
+ if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ LOGGER.debug("10 seconds, {} still not completed. Trying forced shutdown.", executorService.toString());
// those threads will be interrupted in their current task
executorService.shutdownNow();
- if (executorService.awaitTermination(60, TimeUnit.SECONDS)) {
- LOGGER.debug("One minute passed again - forced shutdown of {} worked.", executorService.toString());
+ if (executorService.awaitTermination(10, TimeUnit.SECONDS)) {
+ LOGGER.debug("10 seconds passed again - forced shutdown of {} worked.", executorService.toString());
} else {
LOGGER.error("{} did not terminate", executorService.toString());
}
diff --git a/src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java b/src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java
index 0090539a96..bcb88189ef 100644
--- a/src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java
+++ b/src/main/java/org/jabref/gui/util/DefaultFileUpdateMonitor.java
@@ -8,6 +8,8 @@ import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -32,8 +34,8 @@ public class DefaultFileUpdateMonitor implements Runnable, FileUpdateMonitor {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultFileUpdateMonitor.class);
- private final Multimap<Path, FileUpdateListener> listeners = ArrayListMultimap.create(20, 4);
- private volatile WatchService watcher;
+ volatile WatchService watcher;
+ final Multimap<Path, FileUpdateListener> listeners = ArrayListMultimap.create(20, 4);
private final AtomicBoolean notShutdown = new AtomicBoolean(true);
private final AtomicReference<Optional<JabRefException>> filesystemMonitorFailure = new AtomicReference<>(Optional.empty());
@@ -82,7 +84,12 @@ public class DefaultFileUpdateMonitor implements Runnable, FileUpdateMonitor {
}
private void notifyAboutChange(Path path) {
- listeners.get(path).forEach(FileUpdateListener::fileUpdated);
+ Collection<FileUpdateListener> fileUpdateListeners = Collections.emptyList();
+ while ((path != null) && (fileUpdateListeners.isEmpty())) {
+ fileUpdateListeners = listeners.get(path);
+ path = path.getParent();
+ }
+ fileUpdateListeners.forEach(FileUpdateListener::fileUpdated);
}
@Override
@@ -126,6 +133,7 @@ public class DefaultFileUpdateMonitor implements Runnable, FileUpdateMonitor {
if (watcher != null) {
watcher.close();
}
+ Thread.currentThread().interrupt();
} catch (IOException e) {
LOGGER.error("error closing watcher", e);
}
diff --git a/src/test/java/org/jabref/gui/util/DefaultFileUpdateMonitorTest.java b/src/test/java/org/jabref/gui/util/DefaultFileUpdateMonitorTest.java
new file mode 100644
index 0000000000..95c6235738
--- /dev/null
+++ b/src/test/java/org/jabref/gui/util/DefaultFileUpdateMonitorTest.java
@@ -0,0 +1,40 @@
+package org.jabref.gui.util;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.jabref.gui.JabRefExecutorService;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class DefaultFileUpdateMonitorTest {
+
+ @Test
+ void demonstrateListenerForDirectory(@TempDir Path rootPath) throws Exception {
+ DefaultFileUpdateMonitor fileUpdateMonitor = new DefaultFileUpdateMonitor();
+ JabRefExecutorService.INSTANCE.executeInterruptableTask(fileUpdateMonitor, "FileUpdateMonitor");
+ while (fileUpdateMonitor.watcher == null) {
+ Thread.sleep(100);
+ Thread.yield();
+ }
+ rootPath.register(fileUpdateMonitor.watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY);
+ final AtomicBoolean called = new AtomicBoolean(false);
+ fileUpdateMonitor.listeners.put(rootPath, () -> {
+ called.set(true);
+ });
+ Files.createFile(rootPath.resolve("test.txt"));
+ int callCount = 0;
+ while ((callCount < 10) && (!called.get())) {
+ Thread.sleep(100);
+ Thread.yield();
+ callCount++;
+ }
+ JabRefExecutorService.INSTANCE.shutdownEverything();
+ assertTrue(called.get());
+ }
+}
Maybe, you should add the Path
of the changed file to FileUpdateListener
.
Other approach: implement other classes handling directories and ensure that DefaultFileUpdateMonitor
handles files only.
(JabRef#10585) Changed name from setListerner to updateListener and changed a logger erro message to be more clear. The updateListerner is now called later after the initial search. Lastly refreshLatexDirectory now cancels ongoing searches before reinitializign with init(currentEntry) to do so at most one scan job waits.
@rachedkko Thank you for working on this. Please do not reference the issue/PR in a commit. This is automatically done when pushing to the branch. - Moreover, please check the tests output (or the "Files changed" tab in this PR. You will see some (minor) checkstyle errors. You can also have checkstyle running within IntelliJ. |
I spent work to directly provide Java code updates. Please commit them (if you agree - if you don't, please tell why) Example: (You can see that at https://github.com/JabRef/jabref/pull/10937/files) |
@@ -22,7 +22,7 @@ | |||
import org.jabref.model.database.BibDatabaseContext; | |||
import org.jabref.model.entry.BibEntry; | |||
import org.jabref.preferences.PreferencesService; | |||
|
|||
import org.jabref.gui.util.DefaultFileUpdateMonitor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This import can be removed, can't it?
…Model.java Co-authored-by: Oliver Kopp <[email protected]>
…chedkko/jabref into latexcitation-filemonitor-issue-10585
TBH, currently this is 20% of the work. The real work is following:
Reasons: Energy saving. Very little CPU usage for an update on one file. Although your current method works, it parses ALL Please either work on my proposals above - or "just" fix the compile errors above to have an initial version OKish for an initial student contribution. I think, we won't merge that - and hope for someone to do the hard work... (With the above guidelines it should not be that hard though) |
…chedkko/jabref into latexcitation-filemonitor-issue-10585
Hi, we won't be able to see this issue through sadly, it turned out to be something beyond the time frame given to us for the assignment in the course. But we really appreciate the learning opportunity you all gave us, it was really eye-opening. |
@VinterSallad Thank you for your honest repsonse. We will close the PR and update the issue accordingly. |
Attempts to implement solution so that Latex Citations tab uses file monitoring to refresh itself instead of having to manually refresh after changes to files in the directory.
So far adds DefaultFileUpdateMonitor to the logic for Latex Citation tab and is now therefore able to refresh itself whenever a change happens in the targeted directory.
Closes #10585
CHANGELOG.md
described in a way that is understandable for the average user (if applicable)