Skip to content

Commit e83680f

Browse files
authored
Completely rework save operation (#4309)
* Make FileHistory use Paths instead of strings * Almost completely rewrite save infrastructure * Fix build * Fix tests * Remove file lock * Improve comments * Fix build * Implement feedback and fix tests * Fix build
1 parent c858a94 commit e83680f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1035
-1784
lines changed

Diff for: src/jmh/java/org/jabref/benchmarks/Benchmarks.java

+13-13
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
import java.io.IOException;
44
import java.io.StringReader;
5+
import java.io.StringWriter;
56
import java.util.List;
67
import java.util.Random;
78
import java.util.stream.Collectors;
89

910
import org.jabref.Globals;
1011
import org.jabref.logic.exporter.BibtexDatabaseWriter;
1112
import org.jabref.logic.exporter.SavePreferences;
12-
import org.jabref.logic.exporter.StringSaveSession;
1313
import org.jabref.logic.formatter.bibtexfields.HtmlToLatexFormatter;
14-
import org.jabref.logic.importer.ParseException;
1514
import org.jabref.logic.importer.ParserResult;
1615
import org.jabref.logic.importer.fileformat.BibtexParser;
1716
import org.jabref.logic.layout.format.HTMLChars;
@@ -37,6 +36,8 @@
3736
import org.openjdk.jmh.annotations.State;
3837
import org.openjdk.jmh.runner.RunnerException;
3938

39+
import static org.mockito.Mockito.mock;
40+
4041
@State(Scope.Thread)
4142
public class Benchmarks {
4243

@@ -61,11 +62,11 @@ public void init() throws Exception {
6162
entry.setField("rnd", "2" + randomizer.nextInt());
6263
database.insertEntry(entry);
6364
}
64-
BibtexDatabaseWriter<StringSaveSession> databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new);
65-
StringSaveSession saveSession = databaseWriter.savePartOfDatabase(
66-
new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(),
67-
new SavePreferences());
68-
bibtexString = saveSession.getStringValue();
65+
StringWriter outputWriter = new StringWriter();
66+
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(SavePreferences.class));
67+
databaseWriter.savePartOfDatabase(
68+
new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries());
69+
bibtexString = outputWriter.toString();
6970

7071
latexConversionString = "{A} \\textbf{bold} approach {\\it to} ${{\\Sigma}}{\\Delta}$ modulator \\textsuperscript{2} \\$";
7172

@@ -80,11 +81,10 @@ public ParserResult parse() throws IOException {
8081

8182
@Benchmark
8283
public String write() throws Exception {
83-
BibtexDatabaseWriter<StringSaveSession> databaseWriter = new BibtexDatabaseWriter<>(StringSaveSession::new);
84-
StringSaveSession saveSession = databaseWriter.savePartOfDatabase(
85-
new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries(),
86-
new SavePreferences());
87-
return saveSession.getStringValue();
84+
StringWriter outputWriter = new StringWriter();
85+
BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(outputWriter, mock(SavePreferences.class));
86+
databaseWriter.savePartOfDatabase(new BibDatabaseContext(database, new MetaData(), new Defaults()), database.getEntries());
87+
return outputWriter.toString();
8888
}
8989

9090
@Benchmark
@@ -125,7 +125,7 @@ public String htmlToLatexConversion() {
125125
}
126126

127127
@Benchmark
128-
public boolean keywordGroupContains() throws ParseException {
128+
public boolean keywordGroupContains() {
129129
KeywordGroup group = new WordKeywordGroup("testGroup", GroupHierarchyType.INDEPENDENT, "keyword", "testkeyword", false, ',', false);
130130
return group.containsAll(database.getEntries());
131131
}

Diff for: src/main/java/org/jabref/cli/ArgumentProcessor.java

+25-45
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
import org.jabref.JabRefException;
1717
import org.jabref.gui.externalfiles.AutoSetLinks;
1818
import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator;
19+
import org.jabref.logic.exporter.AtomicFileWriter;
1920
import org.jabref.logic.exporter.BibDatabaseWriter;
2021
import org.jabref.logic.exporter.BibtexDatabaseWriter;
2122
import org.jabref.logic.exporter.Exporter;
2223
import org.jabref.logic.exporter.ExporterFactory;
23-
import org.jabref.logic.exporter.FileSaveSession;
24-
import org.jabref.logic.exporter.SaveException;
2524
import org.jabref.logic.exporter.SavePreferences;
26-
import org.jabref.logic.exporter.SaveSession;
2725
import org.jabref.logic.exporter.TemplateExporter;
2826
import org.jabref.logic.importer.FetcherException;
2927
import org.jabref.logic.importer.ImportException;
@@ -374,27 +372,7 @@ private boolean generateAux(List<ParserResult> loaded, String[] data) {
374372
// write an output, if something could be resolved
375373
if ((newBase != null) && newBase.hasEntries()) {
376374
String subName = StringUtil.getCorrectFileName(data[1], "bib");
377-
378-
try {
379-
System.out.println(Localization.lang("Saving") + ": " + subName);
380-
SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences();
381-
BibDatabaseWriter<SaveSession> databaseWriter = new BibtexDatabaseWriter<>(FileSaveSession::new);
382-
Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode());
383-
SaveSession session = databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults), prefs);
384-
385-
// Show just a warning message if encoding did not work for all characters:
386-
if (!session.getWriter().couldEncodeAll()) {
387-
System.err.println(Localization.lang("Warning") + ": "
388-
+ Localization.lang(
389-
"The chosen encoding '%0' could not encode the following characters:",
390-
session.getEncoding().displayName())
391-
+ " " + session.getWriter().getProblemCharacters());
392-
}
393-
session.commit(subName);
394-
} catch (SaveException ex) {
395-
System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage());
396-
}
397-
375+
saveDatabase(newBase, subName);
398376
notSavedMsg = true;
399377
}
400378

@@ -407,34 +385,36 @@ private boolean generateAux(List<ParserResult> loaded, String[] data) {
407385
}
408386
}
409387

388+
private void saveDatabase(BibDatabase newBase, String subName) {
389+
try {
390+
System.out.println(Localization.lang("Saving") + ": " + subName);
391+
SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences();
392+
AtomicFileWriter fileWriter = new AtomicFileWriter(Paths.get(subName), prefs.getEncoding());
393+
BibDatabaseWriter databaseWriter = new BibtexDatabaseWriter(fileWriter, prefs);
394+
Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode());
395+
databaseWriter.saveDatabase(new BibDatabaseContext(newBase, defaults));
396+
397+
// Show just a warning message if encoding did not work for all characters:
398+
if (fileWriter.hasEncodingProblems()) {
399+
System.err.println(Localization.lang("Warning") + ": "
400+
+ Localization.lang(
401+
"The chosen encoding '%0' could not encode the following characters:",
402+
prefs.getEncoding().displayName())
403+
+ " " + fileWriter.getEncodingProblems());
404+
}
405+
} catch (IOException ex) {
406+
System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage());
407+
}
408+
}
409+
410410
private void exportFile(List<ParserResult> loaded, String[] data) {
411411
if (data.length == 1) {
412412
// This signals that the latest import should be stored in BibTeX
413413
// format to the given file.
414414
if (!loaded.isEmpty()) {
415415
ParserResult pr = loaded.get(loaded.size() - 1);
416416
if (!pr.isInvalid()) {
417-
try {
418-
System.out.println(Localization.lang("Saving") + ": " + data[0]);
419-
SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences();
420-
Defaults defaults = new Defaults(Globals.prefs.getDefaultBibDatabaseMode());
421-
BibDatabaseWriter<SaveSession> databaseWriter = new BibtexDatabaseWriter<>(
422-
FileSaveSession::new);
423-
SaveSession session = databaseWriter.saveDatabase(
424-
new BibDatabaseContext(pr.getDatabase(), pr.getMetaData(), defaults), prefs);
425-
426-
// Show just a warning message if encoding did not work for all characters:
427-
if (!session.getWriter().couldEncodeAll()) {
428-
System.err.println(Localization.lang("Warning") + ": "
429-
+ Localization.lang(
430-
"The chosen encoding '%0' could not encode the following characters:",
431-
session.getEncoding().displayName())
432-
+ " " + session.getWriter().getProblemCharacters());
433-
}
434-
session.commit(data[0]);
435-
} catch (SaveException ex) {
436-
System.err.println(Localization.lang("Could not save file.") + "\n" + ex.getLocalizedMessage());
437-
}
417+
saveDatabase(pr.getDatabase(), data[0]);
438418
}
439419
} else {
440420
System.err.println(Localization.lang("The output option depends on a valid import option."));

Diff for: src/main/java/org/jabref/gui/BasePanel.java

+4-149
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import java.io.IOException;
55
import java.io.StringReader;
66
import java.lang.reflect.InvocationTargetException;
7-
import java.nio.charset.Charset;
8-
import java.nio.charset.UnsupportedCharsetException;
97
import java.nio.file.Path;
108
import java.util.ArrayList;
119
import java.util.Collections;
@@ -17,8 +15,6 @@
1715
import java.util.Set;
1816
import java.util.stream.Collectors;
1917

20-
import javax.swing.JOptionPane;
21-
import javax.swing.JTextArea;
2218
import javax.swing.SwingUtilities;
2319
import javax.swing.undo.CannotRedoException;
2420
import javax.swing.undo.CannotUndoException;
@@ -73,27 +69,17 @@
7369
import org.jabref.gui.undo.UndoableChangeType;
7470
import org.jabref.gui.undo.UndoableFieldChange;
7571
import org.jabref.gui.undo.UndoableInsertEntry;
76-
import org.jabref.gui.undo.UndoableKeyChange;
7772
import org.jabref.gui.undo.UndoableRemoveEntry;
7873
import org.jabref.gui.util.DefaultTaskExecutor;
79-
import org.jabref.gui.util.FileDialogConfiguration;
8074
import org.jabref.gui.worker.CitationStyleToClipboardWorker;
8175
import org.jabref.gui.worker.SendAsEMailAction;
82-
import org.jabref.logic.bibtexkeypattern.BibtexKeyGenerator;
8376
import org.jabref.logic.citationstyle.CitationStyleCache;
8477
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
85-
import org.jabref.logic.exporter.BibtexDatabaseWriter;
86-
import org.jabref.logic.exporter.FileSaveSession;
87-
import org.jabref.logic.exporter.SaveException;
88-
import org.jabref.logic.exporter.SavePreferences;
89-
import org.jabref.logic.exporter.SaveSession;
90-
import org.jabref.logic.l10n.Encodings;
9178
import org.jabref.logic.l10n.Localization;
9279
import org.jabref.logic.layout.Layout;
9380
import org.jabref.logic.layout.LayoutHelper;
9481
import org.jabref.logic.pdf.FileAnnotationCache;
9582
import org.jabref.logic.search.SearchQuery;
96-
import org.jabref.logic.util.StandardFileType;
9783
import org.jabref.logic.util.UpdateField;
9884
import org.jabref.logic.util.io.FileFinder;
9985
import org.jabref.logic.util.io.FileFinders;
@@ -116,13 +102,10 @@
116102
import org.jabref.model.entry.event.EntryEventSource;
117103
import org.jabref.model.entry.specialfields.SpecialField;
118104
import org.jabref.model.entry.specialfields.SpecialFieldValue;
119-
import org.jabref.model.strings.StringUtil;
120105
import org.jabref.preferences.JabRefPreferences;
121106
import org.jabref.preferences.PreviewPreferences;
122107

123108
import com.google.common.eventbus.Subscribe;
124-
import com.jgoodies.forms.builder.FormBuilder;
125-
import com.jgoodies.forms.layout.FormLayout;
126109
import org.fxmisc.easybind.EasyBind;
127110
import org.fxmisc.easybind.Subscription;
128111
import org.slf4j.Logger;
@@ -298,11 +281,11 @@ private void setupActions() {
298281
actions.put(Actions.EDIT, this::showAndEdit);
299282

300283
// The action for saving a database.
301-
actions.put(Actions.SAVE, saveAction);
284+
actions.put(Actions.SAVE, saveAction::save);
302285

303286
actions.put(Actions.SAVE_AS, saveAction::saveAs);
304287

305-
actions.put(Actions.SAVE_SELECTED_AS_PLAIN, new SaveSelectedAction(SavePreferences.DatabaseSaveType.PLAIN_BIBTEX));
288+
actions.put(Actions.SAVE_SELECTED_AS_PLAIN, saveAction::saveSelectedAsPlain);
306289

307290
// The action for copying selected entries.
308291
actions.put(Actions.COPY, this::copy);
@@ -670,87 +653,9 @@ public void runCommand(final Actions command) {
670653
}
671654
}
672655

673-
/**
674-
* FIXME: high code duplication with {@link SaveDatabaseAction#saveDatabase(File, boolean, Charset)}
675-
*/
676-
private boolean saveDatabase(File file, boolean selectedOnly, Charset encoding,
677-
SavePreferences.DatabaseSaveType saveType)
678-
throws SaveException {
679-
SaveSession session;
680-
final String SAVE_DATABASE = Localization.lang("Save library");
681-
try {
682-
SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences()
683-
.withEncoding(encoding)
684-
.withSaveType(saveType);
685-
686-
BibtexDatabaseWriter<SaveSession> databaseWriter = new BibtexDatabaseWriter<>(
687-
FileSaveSession::new);
688-
if (selectedOnly) {
689-
session = databaseWriter.savePartOfDatabase(bibDatabaseContext, mainTable.getSelectedEntries(), prefs);
690-
} else {
691-
session = databaseWriter.saveDatabase(bibDatabaseContext, prefs);
692-
}
693-
694-
registerUndoableChanges(session);
695-
}
696-
// FIXME: not sure if this is really thrown anywhere
697-
catch (UnsupportedCharsetException ex) {
698-
frame.getDialogService().showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file.")
699-
+ Localization.lang("Character encoding '%0' is not supported.", encoding.displayName()));
700-
throw new SaveException("rt");
701-
} catch (SaveException ex) {
702-
if (ex.specificEntry()) {
703-
// Error occurred during processing of the entry. Highlight it:
704-
clearAndSelect(ex.getEntry());
705-
showAndEdit(ex.getEntry());
706-
} else {
707-
LOGGER.warn("Could not save", ex);
708-
}
709-
710-
dialogService.showErrorDialogAndWait(SAVE_DATABASE, Localization.lang("Could not save file."), ex);
711-
throw new SaveException("rt");
712-
}
713-
714-
boolean commit = true;
715-
if (!session.getWriter().couldEncodeAll()) {
716-
FormBuilder builder = FormBuilder.create()
717-
.layout(new FormLayout("left:pref, 4dlu, fill:pref", "pref, 4dlu, pref"));
718-
JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
719-
ta.setEditable(false);
720-
builder.add(Localization.lang("The chosen encoding '%0' could not encode the following characters:", session.getEncoding().displayName())).xy(1, 1);
721-
builder.add(ta).xy(3, 1);
722-
builder.add(Localization.lang("What do you want to do?")).xy(1, 3);
723-
String tryDiff = Localization.lang("Try different encoding");
724-
int answer = JOptionPane.showOptionDialog(null, builder.getPanel(), SAVE_DATABASE, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] {Localization.lang("Save"), tryDiff, Localization.lang("Cancel")}, tryDiff);
725-
726-
if (answer == JOptionPane.NO_OPTION) {
727-
728-
// The user wants to use another encoding.
729-
Object choice = JOptionPane.showInputDialog(null, Localization.lang("Select encoding"), SAVE_DATABASE, JOptionPane.QUESTION_MESSAGE, null, Encodings.ENCODINGS_DISPLAYNAMES, encoding);
730-
if (choice == null) {
731-
commit = false;
732-
} else {
733-
Charset newEncoding = Charset.forName((String) choice);
734-
return saveDatabase(file, selectedOnly, newEncoding, saveType);
735-
}
736-
} else if (answer == JOptionPane.CANCEL_OPTION) {
737-
commit = false;
738-
}
739-
}
740-
741-
if (commit) {
742-
session.commit(file.toPath());
743-
this.bibDatabaseContext.getMetaData().setEncoding(encoding); // Make sure to remember which encoding we used.
744-
} else {
745-
session.cancel();
746-
}
747-
748-
return commit;
749-
}
750-
751-
public void registerUndoableChanges(SaveSession session) {
656+
public void registerUndoableChanges(List<FieldChange> changes) {
752657
NamedCompound ce = new NamedCompound(Localization.lang("Save actions"));
753-
for (FieldChange change : session.getFieldChanges()) {
658+
for (FieldChange change : changes) {
754659
ce.addEdit(new UndoableFieldChange(change));
755660
}
756661
ce.end();
@@ -1207,30 +1112,6 @@ public boolean showDeleteConfirmationDialog(int numberOfEntries) {
12071112
}
12081113
}
12091114

1210-
/**
1211-
* If the relevant option is set, autogenerate keys for all entries that are lacking keys.
1212-
*/
1213-
public void autoGenerateKeysBeforeSaving() {
1214-
if (Globals.prefs.getBoolean(JabRefPreferences.GENERATE_KEYS_BEFORE_SAVING)) {
1215-
NamedCompound ce = new NamedCompound(Localization.lang("Autogenerate BibTeX keys"));
1216-
1217-
BibtexKeyGenerator keyGenerator = new BibtexKeyGenerator(bibDatabaseContext, Globals.prefs.getBibtexKeyPatternPreferences());
1218-
for (BibEntry bes : bibDatabaseContext.getDatabase().getEntries()) {
1219-
Optional<String> oldKey = bes.getCiteKeyOptional();
1220-
if (StringUtil.isBlank(oldKey)) {
1221-
Optional<FieldChange> change = keyGenerator.generateAndSetKey(bes);
1222-
change.ifPresent(fieldChange -> ce.addEdit(new UndoableKeyChange(fieldChange)));
1223-
}
1224-
}
1225-
1226-
// Store undo information, if any:
1227-
if (ce.hasEdits()) {
1228-
ce.end();
1229-
getUndoManager().addEdit(ce);
1230-
}
1231-
}
1232-
}
1233-
12341115
/**
12351116
* Depending on whether a preview or an entry editor is showing, save the current divider location in the correct preference setting.
12361117
*/
@@ -1597,30 +1478,4 @@ public void action() {
15971478
preview.print();
15981479
}
15991480
}
1600-
1601-
private class SaveSelectedAction implements BaseAction {
1602-
1603-
private final SavePreferences.DatabaseSaveType saveType;
1604-
1605-
public SaveSelectedAction(SavePreferences.DatabaseSaveType saveType) {
1606-
this.saveType = saveType;
1607-
}
1608-
1609-
@Override
1610-
public void action() throws SaveException {
1611-
FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
1612-
.withDefaultExtension(StandardFileType.BIBTEX_DB)
1613-
.addExtensionFilter(String.format("%1s %2s", "BibTex", Localization.lang("Library")), StandardFileType.BIBTEX_DB)
1614-
.withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY))
1615-
.build();
1616-
1617-
Optional<Path> chosenFile = dialogService.showFileSaveDialog(fileDialogConfiguration);
1618-
if (chosenFile.isPresent()) {
1619-
Path path = chosenFile.get();
1620-
saveDatabase(path.toFile(), true, Globals.prefs.getDefaultEncoding(), saveType);
1621-
frame.getFileHistory().newFile(path.toString());
1622-
frame.output(Localization.lang("Saved selected to '%0'.", path.toString()));
1623-
}
1624-
}
1625-
}
16261481
}

0 commit comments

Comments
 (0)