Skip to content

Commit a0ca875

Browse files
Fixes Shared Database: Changes filtering in CoarseChangeFilter to attribute property (#6868)
1 parent 3123090 commit a0ca875

File tree

10 files changed

+131
-48
lines changed

10 files changed

+131
-48
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Remote Storage
2+
3+
## Using a shared PostgreSQL database
4+
5+
...
6+
7+
## Handling large shared databases
8+
9+
Synchronization times may get long when working with a large database containing several thousand entries. Therefore, synchronization only happens if several conditions are fulfilled:
10+
11+
* Edit to another field.
12+
* Major changes have been made (pasting or deleting more than one character).
13+
14+
Class `org.jabref.logic.util.CoarseChangeFilter.java` checks both conditions.
15+
16+
Remaining changes that has not been synchronized yet are saved at closing the database rendering additional closing time. Saving is realized in `org.jabref.logic.shared.DBMSSynchronizer.java`. Following methods account for synchronization modes:
17+
18+
* `pullChanges` synchronizes the database unconditionally.
19+
* `pullLastEntryChanges` synchronizes only if there are remaining entry changes. It is invoked when closing the shared database (`closeSharedDatabase`).
20+

src/main/java/org/jabref/logic/autosaveandbackup/AutosaveManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ private AutosaveManager(BibDatabaseContext bibDatabaseContext) {
4242

4343
@Subscribe
4444
public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) {
45+
if (!event.isFilteredOut()) {
46+
startAutosaveTask();
47+
}
48+
}
49+
50+
private void startAutosaveTask() {
4551
throttler.schedule(() -> {
4652
eventBus.post(new AutosaveEvent());
4753
});

src/main/java/org/jabref/logic/autosaveandbackup/BackupManager.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ private void logIfCritical(Path backupPath, IOException e) {
156156

157157
@Subscribe
158158
public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) {
159-
startBackupTask();
159+
if (!event.isFilteredOut()) {
160+
startBackupTask();
161+
}
160162
}
161163

162164
private void startBackupTask() {

src/main/java/org/jabref/logic/shared/DBMSSynchronizer.java

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public class DBMSSynchronizer implements DatabaseSynchronizer {
5454
private final Character keywordSeparator;
5555
private final GlobalCitationKeyPattern globalCiteKeyPattern;
5656
private final FileUpdateMonitor fileMonitor;
57+
private Optional<BibEntry> lastEntryChanged;
5758

5859
public DBMSSynchronizer(BibDatabaseContext bibDatabaseContext, Character keywordSeparator,
5960
GlobalCitationKeyPattern globalCiteKeyPattern, FileUpdateMonitor fileMonitor) {
@@ -64,6 +65,7 @@ public DBMSSynchronizer(BibDatabaseContext bibDatabaseContext, Character keyword
6465
this.eventBus = new EventBus();
6566
this.keywordSeparator = keywordSeparator;
6667
this.globalCiteKeyPattern = Objects.requireNonNull(globalCiteKeyPattern);
68+
this.lastEntryChanged = Optional.empty();
6769
}
6870

6971
/**
@@ -77,10 +79,13 @@ public void listen(EntriesAddedEvent event) {
7779
// In this case DBSynchronizer should not try to insert the bibEntry entry again (but it would not harm).
7880
if (isEventSourceAccepted(event) && checkCurrentConnection()) {
7981
synchronizeLocalMetaData();
80-
synchronizeLocalDatabase(); // Pull changes for the case that there were some
82+
pullWithLastEntry();
83+
synchronizeLocalDatabase();
8184
dbmsProcessor.insertEntries(event.getBibEntries());
82-
}
85+
// Reset last changed entry because it just has already been synchronized -> Why necessary?
86+
lastEntryChanged = Optional.empty();
8387
}
88+
}
8489

8590
/**
8691
* Listening method. Updates an existing shared {@link BibEntry}.
@@ -89,13 +94,17 @@ public void listen(EntriesAddedEvent event) {
8994
*/
9095
@Subscribe
9196
public void listen(FieldChangedEvent event) {
97+
BibEntry bibEntry = event.getBibEntry();
9298
// While synchronizing the local database (see synchronizeLocalDatabase() below), some EntriesEvents may be posted.
9399
// In this case DBSynchronizer should not try to update the bibEntry entry again (but it would not harm).
94-
if (isPresentLocalBibEntry(event.getBibEntry()) && isEventSourceAccepted(event) && checkCurrentConnection()) {
100+
if (isPresentLocalBibEntry(bibEntry) && isEventSourceAccepted(event) && checkCurrentConnection() && !event.isFilteredOut()) {
95101
synchronizeLocalMetaData();
96-
BibEntry bibEntry = event.getBibEntry();
102+
pullWithLastEntry();
97103
synchronizeSharedEntry(bibEntry);
98104
synchronizeLocalDatabase(); // Pull changes for the case that there were some
105+
} else {
106+
// Set new BibEntry that has been changed last
107+
lastEntryChanged = Optional.of(bibEntry);
99108
}
100109
}
101110

@@ -110,10 +119,10 @@ public void listen(EntriesRemovedEvent event) {
110119
// While synchronizing the local database (see synchronizeLocalDatabase() below), some EntriesEvents may be posted.
111120
// In this case DBSynchronizer should not try to delete the bibEntry entry again (but it would not harm).
112121
if (isEventSourceAccepted(event) && checkCurrentConnection()) {
113-
List<BibEntry> entries = event.getBibEntries();
114-
dbmsProcessor.removeEntries(entries);
115122
synchronizeLocalMetaData();
116-
synchronizeLocalDatabase(); // Pull changes for the case that there where some
123+
pullWithLastEntry();
124+
dbmsProcessor.removeEntries(event.getBibEntries());
125+
synchronizeLocalDatabase();
117126
}
118127
}
119128

@@ -313,11 +322,32 @@ public void pullChanges() {
313322
if (!checkCurrentConnection()) {
314323
return;
315324
}
316-
325+
// First synchronize entry, then synchronize database
326+
pullWithLastEntry();
317327
synchronizeLocalDatabase();
318328
synchronizeLocalMetaData();
319329
}
320330

331+
// Synchronizes local BibEntries only if last entry changes still remain
332+
public void pullLastEntryChanges() {
333+
if (!lastEntryChanged.isEmpty()) {
334+
if (!checkCurrentConnection()) {
335+
return;
336+
}
337+
synchronizeLocalMetaData();
338+
pullWithLastEntry();
339+
synchronizeLocalDatabase(); // Pull changes for the case that there were some
340+
}
341+
}
342+
343+
// Synchronizes local BibEntries and pulls remaining last entry changes
344+
private void pullWithLastEntry() {
345+
if (!lastEntryChanged.isEmpty() && isPresentLocalBibEntry(lastEntryChanged.get())) {
346+
synchronizeSharedEntry(lastEntryChanged.get());
347+
}
348+
lastEntryChanged = Optional.empty();
349+
}
350+
321351
/**
322352
* Checks whether the current SQL connection is valid. In case that the connection is not valid a new {@link
323353
* ConnectionLostEvent} is going to be sent.
@@ -360,6 +390,8 @@ public void openSharedDatabase(DatabaseConnection connection) throws DatabaseNot
360390

361391
@Override
362392
public void closeSharedDatabase() {
393+
// Submit remaining entry changes
394+
pullLastEntryChanges();
363395
try {
364396
dbmsProcessor.stopNotificationListener();
365397
currentConnection.close();

src/main/java/org/jabref/logic/util/CoarseChangeFilter.java

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import org.jabref.model.database.BibDatabaseContext;
66
import org.jabref.model.database.event.BibDatabaseContextChangedEvent;
7+
import org.jabref.model.entry.BibEntry;
78
import org.jabref.model.entry.event.FieldChangedEvent;
89
import org.jabref.model.entry.field.Field;
910

@@ -17,54 +18,44 @@ public class CoarseChangeFilter {
1718

1819
private final BibDatabaseContext context;
1920
private final EventBus eventBus = new EventBus();
20-
private final DelayTaskThrottler delayPost;
2121

2222
private Optional<Field> lastFieldChanged;
23+
private Optional<BibEntry> lastEntryChanged;
2324
private int totalDelta;
2425

2526
public CoarseChangeFilter(BibDatabaseContext bibDatabaseContext) {
2627
// Listen for change events
27-
bibDatabaseContext.getDatabase().registerListener(this);
28-
bibDatabaseContext.getMetaData().registerListener(this);
2928
this.context = bibDatabaseContext;
30-
// Delay event post by 5 seconds
31-
this.delayPost = new DelayTaskThrottler(5000);
29+
context.getDatabase().registerListener(this);
30+
context.getMetaData().registerListener(this);
3231
this.lastFieldChanged = Optional.empty();
33-
this.totalDelta = 0;
32+
this.lastEntryChanged = Optional.empty();
3433
}
3534

3635
@Subscribe
3736
public synchronized void listen(BibDatabaseContextChangedEvent event) {
38-
Runnable eventPost = () -> {
39-
// Reset total change delta
40-
totalDelta = 0;
41-
// Post event
42-
eventBus.post(event);
43-
};
4437

45-
if (!(event instanceof FieldChangedEvent)) {
46-
eventPost.run();
47-
} else {
38+
if (event instanceof FieldChangedEvent) {
4839
// Only relay event if the field changes are more than one character or a new field is edited
4940
FieldChangedEvent fieldChange = (FieldChangedEvent) event;
50-
// Sum up change delta
51-
totalDelta += fieldChange.getDelta();
52-
53-
// If editing is started
54-
boolean isNewEdit = lastFieldChanged.isEmpty();
55-
// If other field is edited
56-
boolean isEditOnOtherField = !isNewEdit && !lastFieldChanged.get().equals(fieldChange.getField());
57-
// Only deltas of 1 registered by fieldChange, major change means editing much content
58-
boolean isMajorChange = totalDelta >= 100;
59-
60-
if ((isEditOnOtherField && !isNewEdit) || isMajorChange) {
61-
// Submit old changes immediately
62-
eventPost.run();
63-
} else {
64-
delayPost.schedule(eventPost);
65-
}
66-
// Set new last field
41+
42+
// If editing has started
43+
boolean isNewEdit = lastFieldChanged.isEmpty() || lastEntryChanged.isEmpty();
44+
45+
boolean isChangedField = lastFieldChanged.filter(f -> !f.equals(fieldChange.getField())).isPresent();
46+
boolean isChangedEntry = lastEntryChanged.filter(e -> !e.equals(fieldChange.getBibEntry())).isPresent();
47+
boolean isEditChanged = !isNewEdit && (isChangedField || isChangedEntry);
48+
// Only deltas of 1 when typing in manually, major change means pasting something (more than one character)
49+
boolean isMajorChange = fieldChange.getDelta() > 1;
50+
51+
fieldChange.setFilteredOut(!(isEditChanged || isMajorChange));
52+
// Post each FieldChangedEvent - even the ones being marked as "filtered"
53+
eventBus.post(fieldChange);
54+
6755
lastFieldChanged = Optional.of(fieldChange.getField());
56+
lastEntryChanged = Optional.of(fieldChange.getBibEntry());
57+
} else {
58+
eventBus.post(event);
6859
}
6960
}
7061

src/main/java/org/jabref/logic/util/DelayTaskThrottler.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public class DelayTaskThrottler {
2525
private static final Logger LOGGER = LoggerFactory.getLogger(DelayTaskThrottler.class);
2626

2727
private final ScheduledThreadPoolExecutor executor;
28-
private final int delay;
28+
29+
private int delay;
2930

3031
private ScheduledFuture<?> scheduledTask;
3132

@@ -41,7 +42,7 @@ public DelayTaskThrottler(int delay) {
4142

4243
public ScheduledFuture<?> schedule(Runnable command) {
4344
if (scheduledTask != null) {
44-
scheduledTask.cancel(false);
45+
cancel();
4546
}
4647
try {
4748
scheduledTask = executor.schedule(command, delay, TimeUnit.MILLISECONDS);
@@ -51,9 +52,20 @@ public ScheduledFuture<?> schedule(Runnable command) {
5152
return scheduledTask;
5253
}
5354

55+
// Execute scheduled Runnable early
56+
public void execute(Runnable command) {
57+
delay = 0;
58+
schedule(command);
59+
}
60+
61+
// Cancel scheduled Runnable gracefully
62+
public void cancel() {
63+
scheduledTask.cancel(false);
64+
}
65+
5466
public <T> ScheduledFuture<?> scheduleTask(Callable<?> command) {
5567
if (scheduledTask != null) {
56-
scheduledTask.cancel(false);
68+
cancel();
5769
}
5870
try {
5971
scheduledTask = executor.schedule(command, delay, TimeUnit.MILLISECONDS);
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
package org.jabref.model.database.event;
22

3+
import org.jabref.model.entry.event.EntriesEvent;
34
import org.jabref.model.groups.event.GroupUpdatedEvent;
45
import org.jabref.model.metadata.event.MetaDataChangedEvent;
56

67
/**
78
* This Event is automatically fired at the same time as {@link EntriesEvent}, {@link GroupUpdatedEvent} or {@link MetaDataChangedEvent}.
89
*/
910
public class BibDatabaseContextChangedEvent {
10-
// no data
11+
// If the event has been filtered out
12+
private boolean filteredOut;
13+
14+
public BibDatabaseContextChangedEvent() {
15+
this(false);
16+
}
17+
18+
public BibDatabaseContextChangedEvent(boolean filteredOut) {
19+
this.filteredOut = filteredOut;
20+
}
21+
22+
public boolean isFilteredOut() {
23+
return filteredOut;
24+
}
25+
26+
public void setFilteredOut(boolean filtered) {
27+
this.filteredOut = filtered;
28+
}
1129
}

src/main/java/org/jabref/model/entry/event/EntriesEvent.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public EntriesEvent(List<BibEntry> bibEntries) {
2626
* @param location Location affected by this event
2727
*/
2828
public EntriesEvent(List<BibEntry> bibEntries, EntriesEventSource location) {
29+
super();
2930
this.bibEntries = Objects.requireNonNull(bibEntries);
3031
this.location = Objects.requireNonNull(location);
3132
}

src/main/java/org/jabref/model/entry/event/FieldChangedEvent.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public FieldChangedEvent(BibEntry bibEntry, Field field, String newValue, String
2727
this.field = field;
2828
this.newValue = newValue;
2929
this.oldValue = oldValue;
30-
delta = computeDelta(oldValue, newValue);
30+
this.delta = computeDelta(oldValue, newValue);
3131
}
3232

3333
/**
@@ -40,7 +40,7 @@ public FieldChangedEvent(BibEntry bibEntry, Field field, String newValue, String
4040
this.field = field;
4141
this.newValue = newValue;
4242
this.oldValue = oldValue;
43-
delta = computeDelta(oldValue, newValue);
43+
this.delta = computeDelta(oldValue, newValue);
4444
}
4545

4646
/**
@@ -51,7 +51,7 @@ public FieldChangedEvent(FieldChange fieldChange, EntriesEventSource location) {
5151
this.field = fieldChange.getField();
5252
this.newValue = fieldChange.getNewValue();
5353
this.oldValue = fieldChange.getOldValue();
54-
delta = computeDelta(oldValue, newValue);
54+
this.delta = computeDelta(oldValue, newValue);
5555
}
5656

5757
public FieldChangedEvent(FieldChange fieldChange) {

src/main/java/org/jabref/model/metadata/event/MetaDataChangedEvent.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class MetaDataChangedEvent extends BibDatabaseContextChangedEvent {
1414
* @param metaData Affected instance
1515
*/
1616
public MetaDataChangedEvent(MetaData metaData) {
17+
super();
1718
this.metaData = metaData;
1819
}
1920

0 commit comments

Comments
 (0)