diff --git a/CHANGELOG.md b/CHANGELOG.md index 02f4a673a56..9d23df0e25d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ From the user perspective, the combination of the pull request needs to be teste - We integrated a new three-way merge UI for merging entries in the Entries Merger Dialog, the Duplicate Resolver Dialog, the Entry Importer Dialog, and the External Changes Resolver Dialog. [#8945](https://github.com/JabRef/jabref/pull/8945) - We added the ability to merge groups, keywords, comments and files when merging entries. [#9022](https://github.com/JabRef/jabref/pull/9022) - We added a warning message next to the authors field in the merge dialog to warn users when the authors are the same but formatted differently. [#8745](https://github.com/JabRef/jabref/issues/8745) +- The properties of an existing systematic literature review can be edited. [koppor#604](https://github.com/koppor/jabref/issues/604) +- An SLR can now be started from the SLR itself. [#9131](https://github.com/JabRef/jabref/pull/9131), [koppor#601](https://github.com/koppor/jabref/issues/601) ### Changed @@ -41,6 +43,7 @@ From the user perspective, the combination of the pull request needs to be teste - The global default directory for storing PDFs is now the subdirectory "JabRef" in the user's home. - We reworked the Define study parameters dialog. [#9123](https://github.com/JabRef/jabref/pull/9123) + ### Fixed - We fixed an issue where author names with tilde accents (for example ñ) were marked as "Names are not in the standard BibTex format" [#8071](https://github.com/JabRef/jabref/issues/8071) diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 3861a6af305..49d6a622a34 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -109,6 +109,7 @@ import org.jabref.gui.shared.PullChangesFromSharedAction; import org.jabref.gui.sidepane.SidePane; import org.jabref.gui.sidepane.SidePaneType; +import org.jabref.gui.slr.EditExistingStudyAction; import org.jabref.gui.slr.ExistingStudySearchAction; import org.jabref.gui.slr.StartNewStudyAction; import org.jabref.gui.specialfields.SpecialFieldMenuItemFactory; @@ -541,7 +542,6 @@ private Node createToolbar() { // Setup Toolbar ToolBar toolBar = new ToolBar( - new HBox( factory.createIconButton(StandardActions.NEW_LIBRARY, new NewDatabaseAction(this, prefs)), factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this, prefs, dialogService, stateManager, themeManager)), @@ -874,9 +874,13 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new SendAsEMailAction(dialogService, this.prefs, stateManager)), pushToApplicationMenuItem, + new SeparatorMenuItem(), + + // Systematic Literature Review (SLR) factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs, stateManager, themeManager)), - factory.createMenuItem(StandardActions.SEARCH_FOR_EXISTING_STUDY, new ExistingStudySearchAction(this, Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs, stateManager, themeManager)), + factory.createMenuItem(StandardActions.EDIT_EXISTING_STUDY, new EditExistingStudyAction(this.dialogService, this.stateManager)), + factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(this, this.getOpenDatabaseAction(), this.getDialogService(), Globals.getFileUpdateMonitor(), Globals.TASK_EXECUTOR, prefs, stateManager, themeManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 246b9d554ab..da1c3179e64 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -89,6 +89,7 @@ public class LibraryTab extends Tab { private final ThemeManager themeManager; private final BooleanProperty changedProperty = new SimpleBooleanProperty(false); private final BooleanProperty nonUndoableChangeProperty = new SimpleBooleanProperty(false); + private BibDatabaseContext bibDatabaseContext; private MainTableDataModel tableModel; private CitationStyleCache citationStyleCache; @@ -98,17 +99,22 @@ public class LibraryTab extends Tab { private BasePanelMode mode = BasePanelMode.SHOWING_NOTHING; private SplitPane splitPane; private DatabaseNotification databaseNotificationPane; - private boolean saving; private PersonNameSuggestionProvider searchAutoCompleter; + // Used to track whether the base has changed since last save. private BibEntry showing; + private SuggestionProviders suggestionProviders; + @SuppressWarnings({"FieldCanBeLocal"}) private Subscription dividerPositionSubscription; + // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); + private Optional changeMonitor = Optional.empty(); + // initializing it so we prevent NullPointerException private BackgroundTask dataLoadingTask = BackgroundTask.wrap(() -> null); @@ -213,6 +219,14 @@ public void onDatabaseLoadingSucceed(ParserResult result) { feedData(context); + if (preferencesService.getFilePreferences().shouldFulltextIndexLinkedFiles()) { + try { + indexingTaskManager.updateIndex(PdfIndexer.of(bibDatabaseContext, preferencesService.getFilePreferences()), bibDatabaseContext); + } catch (IOException e) { + LOGGER.error("Cannot access lucene index", e); + } + } + // a temporary workaround to update groups pane stateManager.activeDatabaseProperty().bind( EasyBind.map(frame.getTabbedPane().getSelectionModel().selectedItemProperty(), diff --git a/src/main/java/org/jabref/gui/actions/Action.java b/src/main/java/org/jabref/gui/actions/Action.java index 28859e00225..e6ccfe487b2 100644 --- a/src/main/java/org/jabref/gui/actions/Action.java +++ b/src/main/java/org/jabref/gui/actions/Action.java @@ -1,6 +1,5 @@ package org.jabref.gui.actions; -import java.util.Objects; import java.util.Optional; import org.jabref.gui.icon.JabRefIcon; @@ -20,71 +19,4 @@ default Optional getKeyBinding() { default String getDescription() { return ""; } - - class Builder { - private final ActionImpl actionImpl; - - public Builder(String text) { - this.actionImpl = new ActionImpl(); - setText(text); - } - - public Builder() { - this(""); - } - - public Action setIcon(JabRefIcon icon) { - Objects.requireNonNull(icon); - actionImpl.icon = icon; - return actionImpl; - } - - public Action setText(String text) { - Objects.requireNonNull(text); - actionImpl.text = text; - return actionImpl; - } - - public Action setKeyBinding(KeyBinding keyBinding) { - Objects.requireNonNull(keyBinding); - actionImpl.keyBinding = keyBinding; - return actionImpl; - } - - public Action setDescription(String description) { - Objects.requireNonNull(description); - actionImpl.description = description; - return actionImpl; - } - } - - class ActionImpl implements Action { - private JabRefIcon icon; - private KeyBinding keyBinding; - private String text; - private String description; - - private ActionImpl() { - } - - @Override - public Optional getIcon() { - return Optional.ofNullable(icon); - } - - @Override - public Optional getKeyBinding() { - return Optional.ofNullable(keyBinding); - } - - @Override - public String getText() { - return text != null ? text : ""; - } - - @Override - public String getDescription() { - return description != null ? description : ""; - } - } } diff --git a/src/main/java/org/jabref/gui/actions/ActionHelper.java b/src/main/java/org/jabref/gui/actions/ActionHelper.java index 3b91e686489..5750117e4ad 100644 --- a/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -13,6 +13,7 @@ import org.jabref.gui.StateManager; import org.jabref.logic.shared.DatabaseLocation; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; @@ -33,6 +34,11 @@ public static BooleanExpression needsSharedDatabase(StateManager stateManager) { return BooleanExpression.booleanExpression(binding); } + public static BooleanExpression needsStudyDatabase(StateManager stateManager) { + EasyBinding binding = EasyBind.map(stateManager.activeDatabaseProperty(), context -> context.filter(BibDatabaseContext::isStudy).isPresent()); + return BooleanExpression.booleanExpression(binding); + } + public static BooleanExpression needsEntriesSelected(StateManager stateManager) { return Bindings.isNotEmpty(stateManager.getSelectedEntries()); } diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index b673a4e516b..f37abb775ae 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -87,8 +87,11 @@ public enum StandardActions implements Action { PARSE_LATEX(Localization.lang("Search for citations in LaTeX files..."), IconTheme.JabRefIcons.LATEX_CITATIONS), NEW_SUB_LIBRARY_FROM_AUX(Localization.lang("New sublibrary based on AUX file") + "...", Localization.lang("New BibTeX sublibrary") + Localization.lang("This feature generates a new library based on which entries are needed in an existing LaTeX document."), IconTheme.JabRefIcons.NEW), WRITE_METADATA_TO_PDF(Localization.lang("Write metadata to PDF files"), Localization.lang("Will write metadata to the PDFs linked from selected entries."), KeyBinding.WRITE_METADATA_TO_PDF), + START_NEW_STUDY(Localization.lang("Start new systematic literature review")), - SEARCH_FOR_EXISTING_STUDY(Localization.lang("Perform search for existing systematic literature review")), + UPDATE_SEARCH_RESULTS_OF_STUDY(Localization.lang("Update study search results")), + EDIT_EXISTING_STUDY(Localization.lang("Manage study definition")), + OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")), OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER), OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java index da37e2267ce..55bb2009bbf 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/InfoButton.java @@ -1,5 +1,7 @@ package org.jabref.gui.mergeentries.newmergedialog.cell.sidebuttons; +import java.util.Optional; + import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.control.Button; @@ -9,6 +11,7 @@ import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; import com.tobiasdiez.easybind.EasyBind; @@ -16,18 +19,27 @@ public class InfoButton extends Button { private final StringProperty infoMessage = new SimpleStringProperty(); private final ActionFactory actionFactory = new ActionFactory(Globals.getKeyPrefs()); + private final Action mergeAction = new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.INTEGRITY_INFO); + } + + @Override + public String getText() { + return infoMessage.get(); + } + }; + public InfoButton(String infoMessage) { - setInfoMessage(infoMessage); + this.infoMessage.setValue(infoMessage); configureButton(); - EasyBind.subscribe(infoMessageProperty(), newWarningMessage -> { - configureButton(); - }); + EasyBind.subscribe(this.infoMessage, newWarningMessage -> configureButton()); } private void configureButton() { setMaxHeight(Double.MAX_VALUE); setFocusTraversable(false); - Action mergeAction = new Action.Builder(getInfoMessage()).setIcon(IconTheme.JabRefIcons.INTEGRITY_INFO); actionFactory.configureIconButton(mergeAction, new SimpleCommand() { @Override @@ -36,16 +48,4 @@ public void execute() { } }, this); } - - private void setInfoMessage(String infoMessage) { - infoMessageProperty().set(infoMessage); - } - - public StringProperty infoMessageProperty() { - return infoMessage; - } - - public String getInfoMessage() { - return infoMessage.get(); - } } diff --git a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java index de958837778..4622918ec50 100644 --- a/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java +++ b/src/main/java/org/jabref/gui/mergeentries/newmergedialog/cell/sidebuttons/ToggleMergeUnmergeButton.java @@ -1,5 +1,7 @@ package org.jabref.gui.mergeentries.newmergedialog.cell.sidebuttons; +import java.util.Optional; + import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -11,6 +13,7 @@ import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.icon.IconTheme; +import org.jabref.gui.icon.JabRefIcon; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.field.Field; @@ -32,16 +35,13 @@ public ToggleMergeUnmergeButton(Field field) { } private void configureMergeButton() { - Action mergeAction = new Action.Builder(Localization.lang("Merge %0", field.getDisplayName())) - .setIcon(IconTheme.JabRefIcons.MERGE_GROUPS); - - actionFactory.configureIconButton(mergeAction, new ToggleMergeUnmergeAction(), this); + ToggleMergeCommand mergeCommand = new ToggleMergeCommand(); + actionFactory.configureIconButton(mergeCommand.mergeAction, mergeCommand, this); } private void configureUnmergeButton() { - Action unmergeAction = new Action.Builder(Localization.lang("Unmerge %0", field.getDisplayName())) - .setIcon(IconTheme.JabRefIcons.UNDO); - actionFactory.configureIconButton(unmergeAction, new ToggleMergeUnmergeAction(), this); + ToggleMergeCommand unmergeCommand = new ToggleMergeCommand(); + actionFactory.configureIconButton(unmergeCommand.unmergeAction, unmergeCommand, this); } public ObjectProperty fieldStateProperty() { @@ -71,7 +71,30 @@ public void setCanMerge(boolean value) { canMergeProperty().set(value); } - private class ToggleMergeUnmergeAction extends SimpleCommand { + private class ToggleMergeCommand extends SimpleCommand { + private final Action mergeAction = new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.MERGE_GROUPS); + } + + @Override + public String getText() { + return Localization.lang("Merge %0", field.getDisplayName()); + } + }; + + private final Action unmergeAction = new Action() { + @Override + public Optional getIcon() { + return Optional.of(IconTheme.JabRefIcons.UNDO); + } + + @Override + public String getText() { + return Localization.lang("Unmerge %0", field.getDisplayName()); + } + }; @Override public void execute() { diff --git a/src/main/java/org/jabref/gui/push/PushToLyx.java b/src/main/java/org/jabref/gui/push/PushToLyx.java index 398e2ea265e..12e21c3ca70 100644 --- a/src/main/java/org/jabref/gui/push/PushToLyx.java +++ b/src/main/java/org/jabref/gui/push/PushToLyx.java @@ -46,10 +46,10 @@ public JabRefIcon getIcon() { public void operationCompleted() { if (couldNotConnect) { dialogService.showErrorDialogAndWait(Localization.lang("Error pushing entries"), - Localization.lang("verify that LyX is running and that the lyxpipe is valid") - + ". [" + commandPath + "]"); + Localization.lang("Verify that LyX is running and that the lyxpipe is valid.") + + "[" + commandPath + "]"); } else if (couldNotCall) { - dialogService.showErrorDialogAndWait(Localization.lang("unable to write to") + " " + commandPath + ".in"); + dialogService.showErrorDialogAndWait(Localization.lang("Unable to write to %0.", commandPath + ".in")); } else { super.operationCompleted(); } diff --git a/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java b/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java index 416aa8dd618..9ca6bea4e94 100644 --- a/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java +++ b/src/main/java/org/jabref/gui/search/rules/describer/GrammarBasedSearchRuleDescriber.java @@ -41,8 +41,8 @@ public TextFlow getDescription() { textFlow.getChildren().addAll(descriptionSearchBaseVisitor.visit(parseTree)); textFlow.getChildren().add(TooltipTextUtil.createText(". ", TooltipTextUtil.TextType.NORMAL)); textFlow.getChildren().add(TooltipTextUtil.createText(searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? Localization - .lang("The search is case sensitive.") : - Localization.lang("The search is case insensitive."), TooltipTextUtil.TextType.NORMAL)); + .lang("The search is case-sensitive.") : + Localization.lang("The search is case-insensitive."), TooltipTextUtil.TextType.NORMAL)); return textFlow; } diff --git a/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java b/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java new file mode 100644 index 00000000000..f86eae44389 --- /dev/null +++ b/src/main/java/org/jabref/gui/slr/EditExistingStudyAction.java @@ -0,0 +1,63 @@ +package org.jabref.gui.slr; + +import java.io.IOException; +import java.nio.file.Path; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.logic.crawler.StudyRepository; +import org.jabref.logic.crawler.StudyYamlParser; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.study.Study; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EditExistingStudyAction extends SimpleCommand { + private static final Logger LOGGER = LoggerFactory.getLogger(EditExistingStudyAction.class); + + private final DialogService dialogService; + private final StateManager stateManager; + + public EditExistingStudyAction(DialogService dialogService, StateManager stateManager) { + this.dialogService = dialogService; + this.stateManager = stateManager; + this.executable.bind(ActionHelper.needsStudyDatabase(stateManager)); + } + + @Override + public void execute() { + // The action works on the current library + // This library has to be determined + if (stateManager.getActiveDatabase().isEmpty() || !stateManager.getActiveDatabase().get().isStudy()) { + return; + } + BibDatabaseContext bibDatabaseContext = stateManager.getActiveDatabase().get(); + + // The action can only be called on an existing AND saved study library + // The saving is ensured at creation of a study library + // Thus, this check is only existing to check this assumption + if (bibDatabaseContext.getDatabasePath().isEmpty()) { + LOGGER.error("Library path is not available"); + return; + } + + Path databasePath = bibDatabaseContext.getDatabasePath().get(); + + Path studyDirectory = databasePath.getParent(); + + Study study; + try { + study = new StudyYamlParser().parseStudyYamlFile(studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); + } catch (IOException e) { + dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), e); + return; + } + + // When the dialog returns, the study.yml file is updated (or kept unmodified at Cancel) + dialogService.showCustomDialogAndWait(new ManageStudyDefinitionView(study, studyDirectory)); + } +} diff --git a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java index 284e8c9a487..f61ca0c801a 100644 --- a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java +++ b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java @@ -2,17 +2,15 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.Objects; -import java.util.Optional; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefFrame; import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.theme.ThemeManager; import org.jabref.gui.util.BackgroundTask; -import org.jabref.gui.util.DirectoryDialogConfiguration; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.crawler.Crawler; import org.jabref.logic.exporter.SavePreferences; @@ -21,7 +19,7 @@ import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.ParseException; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.GeneralPreferences; @@ -35,30 +33,50 @@ public class ExistingStudySearchAction extends SimpleCommand { private static final Logger LOGGER = LoggerFactory.getLogger(ExistingStudySearchAction.class); protected final DialogService dialogService; - protected final Path workingDirectory; - Path studyDirectory; + protected Path studyDirectory; + protected final PreferencesService preferencesService; + protected final StateManager stateManager; - private final JabRefFrame frame; private final FileUpdateMonitor fileUpdateMonitor; private final TaskExecutor taskExecutor; - private final PreferencesService preferencesService; - private final StateManager stateManager; private final ThemeManager themeManager; private final GeneralPreferences generalPreferences; private final ImportFormatPreferences importFormatPreferences; private final ImporterPreferences importerPreferences; private final SavePreferences savePreferences; - // This can be either populated before crawl is called or is populated in the call using the directory dialog. This is helpful if the directory is selected in a previous dialog/UI element - - public ExistingStudySearchAction(JabRefFrame frame, - FileUpdateMonitor fileUpdateMonitor, - TaskExecutor taskExecutor, - PreferencesService preferencesService, - StateManager stateManager, - ThemeManager themeManager) { + private final JabRefFrame frame; + private final OpenDatabaseAction openDatabaseAction; + + /** + * @param frame Required to close the tab before the study is updated + * @param openDatabaseAction Required to open the tab after the study is exectued + */ + public ExistingStudySearchAction( + JabRefFrame frame, + OpenDatabaseAction openDatabaseAction, + DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, + PreferencesService preferencesService, + StateManager stateManager, + ThemeManager themeManager) { + this(frame, openDatabaseAction, dialogService, fileUpdateMonitor, taskExecutor, preferencesService, stateManager, themeManager, false); + } + + protected ExistingStudySearchAction( + JabRefFrame frame, + OpenDatabaseAction openDatabaseAction, + DialogService dialogService, + FileUpdateMonitor fileUpdateMonitor, + TaskExecutor taskExecutor, + PreferencesService preferencesService, + StateManager stateManager, + ThemeManager themeManager, + boolean isNew) { this.frame = frame; - this.dialogService = frame.getDialogService(); + this.openDatabaseAction = openDatabaseAction; + this.dialogService = dialogService; this.fileUpdateMonitor = fileUpdateMonitor; this.taskExecutor = taskExecutor; this.preferencesService = preferencesService; @@ -69,46 +87,41 @@ public ExistingStudySearchAction(JabRefFrame frame, this.importerPreferences = preferencesService.getImporterPreferences(); this.savePreferences = preferencesService.getSavePreferences(); - this.workingDirectory = stateManager.getActiveDatabase() - .map(database -> FileUtil.getInitialDirectory( - database, - preferencesService.getFilePreferences().getWorkingDirectory())) - .orElse(preferencesService.getFilePreferences().getWorkingDirectory()); + if (!isNew) { + this.executable.bind(ActionHelper.needsStudyDatabase(stateManager)); + } } @Override public void execute() { - // Reset before each execution - studyDirectory = null; - crawl(); - } - - public void crawl() { - if (Objects.isNull(studyDirectory)) { - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(workingDirectory) - .build(); - - Optional studyRepositoryRoot = dialogService.showDirectorySelectionDialog(directoryDialogConfiguration); + if (stateManager.getActiveDatabase().isEmpty()) { + LOGGER.error("Database is not present, even if it should"); + return; + } + BibDatabaseContext bibDatabaseContext = stateManager.getActiveDatabase().get(); - if (studyRepositoryRoot.isEmpty()) { - // Do nothing if selection was canceled - return; - } - studyDirectory = studyRepositoryRoot.get(); + if (bibDatabaseContext.getDatabasePath().isEmpty()) { + LOGGER.error("Database path is not present, even if it should"); + return; } + this.studyDirectory = bibDatabaseContext.getDatabasePath().get().getParent(); + + crawl(); + } + protected void crawl() { try { - setupRepository(studyDirectory); + crawlPreparation(this.studyDirectory); } catch (IOException | GitAPIException e) { dialogService.showErrorDialogAndWait(Localization.lang("Study repository could not be created"), e); return; } + final Crawler crawler; try { crawler = new Crawler( - studyDirectory, - new SlrGitHandler(studyDirectory), + this.studyDirectory, + new SlrGitHandler(this.studyDirectory), generalPreferences, importFormatPreferences, importerPreferences, @@ -120,7 +133,8 @@ public void crawl() { dialogService.showErrorDialogAndWait(Localization.lang("Error during reading of study definition file."), e); return; } - dialogService.notify(Localization.lang("Searching")); + + dialogService.notify(Localization.lang("Searching...")); BackgroundTask.wrap(() -> { crawler.performCrawl(); return 0; // Return any value to make this a callable instead of a runnable. This allows throwing exceptions. @@ -130,17 +144,21 @@ public void crawl() { dialogService.showErrorDialogAndWait(Localization.lang("Error during persistence of crawling results."), e); }) .onSuccess(unused -> { - new OpenDatabaseAction(frame, preferencesService, dialogService, stateManager, themeManager).openFile(Path.of(studyDirectory.toString(), "studyResult.bib")); - // If finished reset command object for next use - studyDirectory = null; + dialogService.notify(Localization.lang("Finished Searching")); + openDatabaseAction.openFile(Path.of(this.studyDirectory.toString(), Crawler.FILENAME_STUDY_RESULT_BIB)); }) .executeWith(taskExecutor); } /** - * Hook for setting up the repository + * Hook for setting up the crawl phase (e.g., initialization the repository) */ - protected void setupRepository(Path studyRepositoryRoot) throws IOException, GitAPIException { - // Do nothing as repository is already setup + protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, GitAPIException { + // Do nothing with the repository as repository is already setup + + // The user focused an SLR + // We hard close the tab + // Future work: Properly close the tab (with saving, ...) + frame.closeCurrentTab(); } } diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml b/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml index 297ee2c1318..6aef2a901e6 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinition.fxml @@ -278,9 +278,9 @@ + text="%Save"/> diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java index ebd7e89619f..5040015fa3d 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionView.java @@ -1,7 +1,7 @@ package org.jabref.gui.slr; import java.nio.file.Path; -import java.util.Objects; +import java.util.Optional; import java.util.StringJoiner; import java.util.function.Consumer; @@ -50,8 +50,9 @@ public class ManageStudyDefinitionView extends BaseDialog @FXML private TextField addResearchQuestion; @FXML private TextField addQuery; @FXML private TextField studyDirectory; + @FXML private Button selectStudyDirectory; - @FXML private ButtonType saveButtonType; + @FXML private ButtonType saveSurveyButtonType; @FXML private Label helpIcon; @FXML private TableView authorTableView; @@ -75,42 +76,70 @@ public class ManageStudyDefinitionView extends BaseDialog @Inject private ThemeManager themeManager; private ManageStudyDefinitionViewModel viewModel; - private final Study study; - private final Path workingDirectory; + // not present if new study is created; + // present if existing study is edited + private final Optional study; + + // Either the proposed directory (on new study creation) + // or the "real" directory of the study + private final Path pathToStudyDataDirectory; + + /** + * This is used to create a new study + * + * @param pathToStudyDataDirectory This directory is proposed in the file chooser + */ + public ManageStudyDefinitionView(Path pathToStudyDataDirectory) { + this.pathToStudyDataDirectory = pathToStudyDataDirectory; + this.setTitle("Define study parameters"); + this.study = Optional.empty(); + + ViewLoader.view(this) + .load() + .setAsDialogPane(this); + + setupSaveSurveyButton(false); + + themeManager.updateFontStyle(getDialogPane().getScene()); + } /** - * This can be used to either create new study objects or edit existing ones. + * This is used to edit an existing study. * - * @param study null if a new study is created. Otherwise, the study object to edit. - * @param studyDirectory the directory where the study to edit is located (null if a new study is created) + * @param study the study to edit + * @param studyDirectory the directory of the study */ - public ManageStudyDefinitionView(Study study, Path studyDirectory, Path workingDirectory) { - // If an existing study is edited, open the directory dialog at the directory the study is stored - this.workingDirectory = Objects.isNull(studyDirectory) ? workingDirectory : studyDirectory; - this.setTitle(Objects.isNull(studyDirectory) ? Localization.lang("Define study parameters") : Localization.lang("Manage study definition")); - this.study = study; + public ManageStudyDefinitionView(Study study, Path studyDirectory) { + this.pathToStudyDataDirectory = studyDirectory; + this.setTitle(Localization.lang("Manage study definition")); + this.study = Optional.of(study); ViewLoader.view(this) .load() .setAsDialogPane(this); - setupSaveButton(); + setupSaveSurveyButton(true); themeManager.updateFontStyle(getDialogPane().getScene()); } - private void setupSaveButton() { - Button saveButton = ((Button) this.getDialogPane().lookupButton(saveButtonType)); + private void setupSaveSurveyButton(boolean isEdit) { + Button saveSurveyButton = ((Button) this.getDialogPane().lookupButton(saveSurveyButtonType)); - saveButton.disableProperty().bind(Bindings.or(Bindings.or( - Bindings.or( - Bindings.or(Bindings.isEmpty(viewModel.getQueries()), Bindings.isEmpty(viewModel.getDatabases())), - Bindings.isEmpty(viewModel.getAuthors())), - viewModel.getTitle().isEmpty()), viewModel.getDirectory().isEmpty())); + if (!isEdit) { + saveSurveyButton.setText(Localization.lang("Start survey")); + } + + saveSurveyButton.disableProperty().bind(Bindings.or(Bindings.or(Bindings.or(Bindings.or( + Bindings.isEmpty(viewModel.getQueries()), + Bindings.isEmpty(viewModel.getDatabases())), + Bindings.isEmpty(viewModel.getAuthors())), + viewModel.getTitle().isEmpty()), + viewModel.getDirectory().isEmpty())); setResultConverter(button -> { - if (button == saveButtonType) { + if (button == saveSurveyButtonType) { return viewModel.saveStudy(); } // Cancel button will return null @@ -120,13 +149,22 @@ private void setupSaveButton() { @FXML private void initialize() { - if (Objects.isNull(study)) { + if (study.isEmpty()) { viewModel = new ManageStudyDefinitionViewModel( prefs.getImportFormatPreferences(), - prefs.getImporterPreferences()); + prefs.getImporterPreferences(), + dialogService); } else { - LOGGER.error("Not yet implemented"); - return; + viewModel = new ManageStudyDefinitionViewModel( + study.get(), + pathToStudyDataDirectory, + prefs.getImportFormatPreferences(), + prefs.getImporterPreferences(), + dialogService); + + // The directory of the study cannot be changed + studyDirectory.setEditable(false); + selectStudyDirectory.setDisable(true); } // Listen whether any databases are removed from selection -> Add back to the database selector @@ -238,7 +276,7 @@ private void addQuery() { @FXML public void selectStudyDirectory() { DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(workingDirectory) + .withInitialDirectory(pathToStudyDataDirectory) .build(); viewModel.setStudyDirectory(dialogService.showDirectorySelectionDialog(directoryDialogConfiguration)); diff --git a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java index 3f87b2232c3..bc013c2e7f3 100644 --- a/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java +++ b/src/main/java/org/jabref/gui/slr/ManageStudyDefinitionViewModel.java @@ -1,5 +1,6 @@ package org.jabref.gui.slr; +import java.io.IOException; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.List; @@ -14,6 +15,10 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import org.jabref.gui.DialogService; +import org.jabref.logic.crawler.StudyRepository; +import org.jabref.logic.crawler.StudyYamlParser; +import org.jabref.logic.git.GitHandler; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; import org.jabref.logic.importer.SearchBasedFetcher; @@ -23,6 +28,7 @@ import org.jabref.logic.importer.fetcher.DBLPFetcher; import org.jabref.logic.importer.fetcher.IEEE; import org.jabref.logic.importer.fetcher.SpringerFetcher; +import org.jabref.logic.l10n.Localization; import org.jabref.model.study.Study; import org.jabref.model.study.StudyDatabase; import org.jabref.model.study.StudyQuery; @@ -43,7 +49,6 @@ public class ManageStudyDefinitionViewModel { SpringerFetcher.FETCHER_NAME, DBLPFetcher.FETCHER_NAME); - private final StringProperty title = new SimpleStringProperty(); private final ObservableList authors = FXCollections.observableArrayList(); private final ObservableList researchQuestions = FXCollections.observableArrayList(); @@ -53,11 +58,14 @@ public class ManageStudyDefinitionViewModel { // Hold the complement of databases for the selector private final SimpleStringProperty directory = new SimpleStringProperty(); + private final DialogService dialogService; + /** * Constructor for a new study */ public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPreferences, - ImporterPreferences importerPreferences) { + ImporterPreferences importerPreferences, + DialogService dialogService) { databases.addAll(WebFetchers.getSearchBasedFetchers(importFormatPreferences, importerPreferences) .stream() .map(SearchBasedFetcher::getName) @@ -69,18 +77,20 @@ public ManageStudyDefinitionViewModel(ImportFormatPreferences importFormatPrefer return new StudyDatabaseItem(name, enabled); }) .toList()); + this.dialogService = Objects.requireNonNull(dialogService); } /** * Constructor for an existing study * - * @param study The study to initialize the UI from + * @param study The study to initialize the UI from * @param studyDirectory The path where the study resides */ public ManageStudyDefinitionViewModel(Study study, Path studyDirectory, ImportFormatPreferences importFormatPreferences, - ImporterPreferences importerPreferences) { + ImporterPreferences importerPreferences, + DialogService dialogService) { // copy the content of the study object into the UI fields authors.addAll(Objects.requireNonNull(study).getAuthors()); title.setValue(study.getTitle()); @@ -100,6 +110,7 @@ public ManageStudyDefinitionViewModel(Study study, .toList()); this.directory.set(Objects.requireNonNull(studyDirectory).toString()); + this.dialogService = Objects.requireNonNull(dialogService); } public StringProperty getTitle() { @@ -154,13 +165,36 @@ public SlrStudyAndDirectory saveStudy() { researchQuestions, queries.stream().map(StudyQuery::new).collect(Collectors.toList()), databases.stream().map(studyDatabaseItem -> new StudyDatabase(studyDatabaseItem.getName(), studyDatabaseItem.isEnabled())).filter(StudyDatabase::isEnabled).collect(Collectors.toList())); - Path studyDirectory = null; + Path studyDirectory; + final String studyDirectoryAsString = directory.getValueSafe(); + try { + studyDirectory = Path.of(studyDirectoryAsString); + } catch (InvalidPathException e) { + LOGGER.error("Invalid path was provided: {}", studyDirectoryAsString); + dialogService.notify(Localization.lang("Unable to write to %0.", studyDirectoryAsString)); + // We do not assume another path - we return that there is an invalid object. + return null; + } + Path studyDefinitionFile = studyDirectory.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME); try { - studyDirectory = Path.of(directory.getValueSafe()); - } catch ( - InvalidPathException e) { - LOGGER.error("Invalid path was provided: {}", directory); + new StudyYamlParser().writeStudyYamlFile(study, studyDefinitionFile); + } catch (IOException e) { + LOGGER.error("Could not write study file {}", studyDefinitionFile, e); + dialogService.notify(Localization.lang("Please enter a valid file path.") + + ": " + studyDirectoryAsString); + // We do not assume another path - we return that there is an invalid object. + return null; } + + try { + new GitHandler(studyDirectory).createCommitOnCurrentBranch("Update study definition", false); + } catch (Exception e) { + LOGGER.error("Could not commit study definition file in directory {}", studyDirectory, e); + dialogService.notify(Localization.lang("Please enter a valid file path.") + + ": " + studyDirectory); + // We continue nevertheless as the directory itself could be valid + } + return new SlrStudyAndDirectory(study, studyDirectory); } diff --git a/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java b/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java index 2baf1f38791..223d99d5224 100644 --- a/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java +++ b/src/main/java/org/jabref/gui/slr/SlrStudyAndDirectory.java @@ -1,6 +1,7 @@ package org.jabref.gui.slr; import java.nio.file.Path; +import java.util.Objects; import org.jabref.model.study.Study; @@ -9,8 +10,8 @@ public class SlrStudyAndDirectory { private final Path studyDirectory; public SlrStudyAndDirectory(Study study, Path studyDirectory) { - this.study = study; - this.studyDirectory = studyDirectory; + this.study = Objects.requireNonNull(study); + this.studyDirectory = Objects.requireNonNull(studyDirectory); } public Path getStudyDirectory() { diff --git a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java index 0bf2a4902c5..f04f692a84a 100644 --- a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java +++ b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java @@ -10,35 +10,69 @@ import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.crawler.StudyRepository; import org.jabref.logic.crawler.StudyYamlParser; +import org.jabref.logic.git.GitHandler; +import org.jabref.logic.l10n.Localization; import org.jabref.model.study.Study; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; import org.eclipse.jgit.api.errors.GitAPIException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +/** + * Used to start a new study: + *
    + *
  1. Let the user input meta data for the study.
  2. + *
  3. Let JabRef do the crawling afterwards.
  4. + *
+ * + * Needs to inherit {@link ExistingStudySearchAction}, because that action implements the real crawling. + * + * There is the hook {@link StartNewStudyAction#crawlPreparation(Path)}, which is used by {@link ExistingStudySearchAction#crawl()}. + */ public class StartNewStudyAction extends ExistingStudySearchAction { + private static final Logger LOGGER = LoggerFactory.getLogger(StartNewStudyAction.class); + Study newStudy; - public StartNewStudyAction(JabRefFrame frame, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, PreferencesService prefs, StateManager stateManager, ThemeManager themeManager) { - super(frame, fileUpdateMonitor, taskExecutor, prefs, stateManager, themeManager); + public StartNewStudyAction(JabRefFrame frame, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, PreferencesService preferencesService, StateManager stateManager, ThemeManager themeManager) { + super(frame, frame.getOpenDatabaseAction(), frame.getDialogService(), fileUpdateMonitor, taskExecutor, preferencesService, stateManager, themeManager, true); } @Override - protected void setupRepository(Path studyRepositoryRoot) throws IOException, GitAPIException { + protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, GitAPIException { StudyYamlParser studyYAMLParser = new StudyYamlParser(); studyYAMLParser.writeStudyYamlFile(newStudy, studyRepositoryRoot.resolve(StudyRepository.STUDY_DEFINITION_FILE_NAME)); + + // When execution reaches this point, the user created a new study. + // The GitHandler is already called to initialize the repository with one single commit "Initial commit". + // The "Initial commit" should also contain the created YAML. + // Thus, we append to that commit. + new GitHandler(studyRepositoryRoot).createCommitOnCurrentBranch("Initial commit", true); } @Override public void execute() { - Optional studyAndDirectory = dialogService.showCustomDialogAndWait(new ManageStudyDefinitionView(null, null, workingDirectory)); + Optional studyAndDirectory = dialogService.showCustomDialogAndWait( + new ManageStudyDefinitionView(preferencesService.getFilePreferences().getWorkingDirectory())); if (studyAndDirectory.isEmpty()) { return; } - if (!studyAndDirectory.get().getStudyDirectory().toString().isBlank()) { - studyDirectory = studyAndDirectory.get().getStudyDirectory(); + + if (studyAndDirectory.get().getStudyDirectory().toString().isBlank()) { + LOGGER.error("Study directory is blank"); + // This branch probably is never taken + // Thus, we re-use existing localization + dialogService.showErrorDialogAndWait(Localization.lang("Must not be empty!")); + return; } + studyDirectory = studyAndDirectory.get().getStudyDirectory(); + + // set variable for #setupRepository + // setupRepository() is called at crawl() newStudy = studyAndDirectory.get().getStudy(); + crawl(); } } diff --git a/src/main/java/org/jabref/logic/crawler/Crawler.java b/src/main/java/org/jabref/logic/crawler/Crawler.java index 04bed30f0b0..c5ae3cec68e 100644 --- a/src/main/java/org/jabref/logic/crawler/Crawler.java +++ b/src/main/java/org/jabref/logic/crawler/Crawler.java @@ -25,6 +25,8 @@ * and a StudyFetcher that manages the crawling over the selected E-Libraries. */ public class Crawler { + public static final String FILENAME_STUDY_RESULT_BIB = "studyResult.bib"; + private final StudyRepository studyRepository; private final StudyFetcher studyFetcher; diff --git a/src/main/java/org/jabref/logic/crawler/StudyRepository.java b/src/main/java/org/jabref/logic/crawler/StudyRepository.java index 63feea3d825..0194a92f8e9 100644 --- a/src/main/java/org/jabref/logic/crawler/StudyRepository.java +++ b/src/main/java/org/jabref/logic/crawler/StudyRepository.java @@ -1,8 +1,7 @@ package org.jabref.logic.crawler; -import java.io.FileWriter; import java.io.IOException; -import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Files; import java.nio.file.Path; @@ -14,6 +13,7 @@ import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.database.DatabaseMerger; +import org.jabref.logic.exporter.AtomicFileWriter; import org.jabref.logic.exporter.BibWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SaveException; @@ -53,8 +53,10 @@ public class StudyRepository { public static final String STUDY_DEFINITION_FILE_NAME = "study.yml"; private static final Logger LOGGER = LoggerFactory.getLogger(StudyRepository.class); - private static final Pattern MATCHCOLON = Pattern.compile(":"); - private static final Pattern MATCHILLEGALCHARACTERS = Pattern.compile("[^A-Za-z0-9_.\\s=-]"); + + private static final Pattern MATCH_COLON = Pattern.compile(":"); + private static final Pattern MATCH_ILLEGAL_CHARACTERS = Pattern.compile("[^A-Za-z0-9_.\\s=-]"); + // Currently we make assumptions about the configuration: the remotes, work and search branch names private static final String REMOTE = "origin"; private static final String WORK_BRANCH = "work"; @@ -75,7 +77,7 @@ public class StudyRepository { * Creates a study repository. * * @param pathToRepository Where the repository root is located. - * @param gitHandler The git handler that managages any interaction with the remote repository + * @param gitHandler The git handler that manages any interaction with the remote repository * @throws IllegalArgumentException If the repository root directory does not exist, or the root directory does not * contain the study definition file. * @throws IOException Thrown if the given repository does not exists, or the study definition file @@ -114,17 +116,20 @@ public StudyRepository(Path pathToRepository, } study = parseStudyFile(); try { + final String updateRepositoryStructureMessage = "Update repository structure"; + // Update repository structure on work branch in case of changes - setUpRepositoryStructure(); - gitHandler.createCommitOnCurrentBranch("Setup/Update Repository Structure", false); + setUpRepositoryStructureForQueriesAndFetchers(); + gitHandler.createCommitOnCurrentBranch(updateRepositoryStructureMessage, false); + gitHandler.checkoutBranch(SEARCH_BRANCH); // If study definition does not exist on this branch or was changed on work branch, copy it from work boolean studyDefinitionDoesNotExistOrChanged = !(Files.exists(studyDefinitionFile) && new StudyYamlParser().parseStudyYamlFile(studyDefinitionFile).equals(study)); if (studyDefinitionDoesNotExistOrChanged) { new StudyYamlParser().writeStudyYamlFile(study, studyDefinitionFile); } - this.setUpRepositoryStructure(); - gitHandler.createCommitOnCurrentBranch("Setup/Update Repository Structure", false); + setUpRepositoryStructureForQueriesAndFetchers(); + gitHandler.createCommitOnCurrentBranch(updateRepositoryStructureMessage, false); } catch (GitAPIException e) { LOGGER.error("Could not checkout search branch."); } @@ -218,6 +223,7 @@ public Study getStudy() { */ public void persist(List crawlResults) throws IOException, GitAPIException, SaveException { updateWorkAndSearchBranch(); + gitHandler.checkoutBranch(SEARCH_BRANCH); persistResults(crawlResults); try { @@ -276,7 +282,7 @@ private void updateWorkAndSearchBranch() throws IOException, GitAPIException { /** * Create for each query a folder, and for each fetcher a bib file in the query folder to store its results. */ - private void setUpRepositoryStructure() throws IOException { + private void setUpRepositoryStructureForQueriesAndFetchers() throws IOException { // Cannot use stream here since IOException has to be thrown StudyDatabaseToFetcherConverter converter = new StudyDatabaseToFetcherConverter( this.getActiveLibraryEntries(), @@ -348,13 +354,15 @@ private void createBibFile(Path file) { * Input: '"test driven"' as a query entry with id 12348765 * Output: '12348765 - test driven' * + * Note that this method might be similar to {@link org.jabref.logic.util.io.FileUtil#getValidFileName(String)} or {@link org.jabref.logic.util.io.FileNameCleaner#cleanFileName(String)} + * * @param query that is trimmed and combined with its query id * @return a unique folder name for any query. */ private String trimNameAndAddID(String query) { // Replace all field: with field= for folder name - String trimmedNamed = MATCHCOLON.matcher(query).replaceAll("="); - trimmedNamed = MATCHILLEGALCHARACTERS.matcher(trimmedNamed).replaceAll(""); + String trimmedNamed = MATCH_COLON.matcher(query).replaceAll("="); + trimmedNamed = MATCH_ILLEGAL_CHARACTERS.matcher(trimmedNamed).replaceAll(""); String id = computeIDForQuery(query); // Whole path has to be shorter than 260 int remainingPathLength = 220 - studyDefinitionFile.toString().length() - id.length(); @@ -419,34 +427,31 @@ private void generateCiteKeys(BibDatabaseContext existingEntries, BibDatabase ta targetEntries.getEntries().stream().filter(bibEntry -> !bibEntry.hasCitationKey()).forEach(citationKeyGenerator::generateAndSetKey); } - private void writeResultToFile(Path pathToFile, BibDatabase entries) throws IOException, SaveException { - if (!Files.exists(pathToFile)) { - Files.createFile(pathToFile); - } - try (Writer fileWriter = new FileWriter(pathToFile.toFile())) { + private void writeResultToFile(Path pathToFile, BibDatabase entries) throws SaveException { + try (AtomicFileWriter fileWriter = new AtomicFileWriter(pathToFile, StandardCharsets.UTF_8)) { BibWriter bibWriter = new BibWriter(fileWriter, OS.NEWLINE); BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter(bibWriter, generalPreferences, savePreferences, bibEntryTypesManager); databaseWriter.saveDatabase(new BibDatabaseContext(entries)); } catch (UnsupportedCharsetException ex) { throw new SaveException(Localization.lang("Character encoding UTF-8 is not supported.", ex)); } catch (IOException ex) { - throw new SaveException("Problems saving: " + ex, ex); + throw new SaveException("Problems saving", ex); } } private Path getPathToFetcherResultFile(String query, String fetcherName) { - return Path.of(repositoryPath.toString(), trimNameAndAddID(query), FileNameCleaner.cleanFileName(fetcherName) + ".bib"); + return repositoryPath.resolve(trimNameAndAddID(query)).resolve(FileNameCleaner.cleanFileName(fetcherName) + ".bib"); } private Path getPathToQueryResultFile(String query) { - return Path.of(repositoryPath.toString(), trimNameAndAddID(query), "result.bib"); + return repositoryPath.resolve(trimNameAndAddID(query)).resolve("result.bib"); } private Path getPathToStudyResultFile() { - return Path.of(repositoryPath.toString(), "studyResult.bib"); + return repositoryPath.resolve(Crawler.FILENAME_STUDY_RESULT_BIB); } private Path getPathToQueryDirectory(String query) { - return Path.of(repositoryPath.toString(), trimNameAndAddID(query)); + return repositoryPath.resolve(trimNameAndAddID(query)); } } diff --git a/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java b/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java index db1bb1b9a9f..618dd18c42b 100644 --- a/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java +++ b/src/main/java/org/jabref/logic/crawler/StudyYamlParser.java @@ -11,6 +11,9 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +/** + * Example use: new StudyYamlParser().parseStudyYamlFile(studyDefinitionFile); + */ public class StudyYamlParser { /** diff --git a/src/main/java/org/jabref/logic/git/GitHandler.java b/src/main/java/org/jabref/logic/git/GitHandler.java index 7ad89689672..498e5c5f3df 100644 --- a/src/main/java/org/jabref/logic/git/GitHandler.java +++ b/src/main/java/org/jabref/logic/git/GitHandler.java @@ -22,7 +22,7 @@ /** * This class handles the updating of the local and remote git repository that is located at the repository path - * This provides an easy to use interface to manage a git repository + * This provides an easy-to-use interface to manage a git repository */ public class GitHandler { static final Logger LOGGER = LoggerFactory.getLogger(GitHandler.class); @@ -44,14 +44,20 @@ public GitHandler(Path repositoryPath) { try { Git.init() .setDirectory(repositoryPathAsFile) + .setInitialBranch("main") .call(); - try (Git git = Git.open(repositoryPathAsFile)) { - git.commit() - .setAllowEmpty(true) - .setMessage("Initial commit") - .call(); - } setupGitIgnore(); + String initialCommit = "Initial commit"; + if (!createCommitOnCurrentBranch(initialCommit, false)) { + // Maybe, setupGitIgnore failed and did not add something + // Then, we create an empty commit + try (Git git = Git.open(repositoryPathAsFile)) { + git.commit() + .setAllowEmpty(true) + .setMessage(initialCommit) + .call(); + } + } } catch (GitAPIException | IOException e) { LOGGER.error("Initialization failed"); } @@ -81,7 +87,7 @@ boolean isGitRepository() { /** * Checkout the branch with the specified name, if it does not exist create it * - * @param branchToCheckout Name of the branch to checkout + * @param branchToCheckout Name of the branch to check out */ public void checkoutBranch(String branchToCheckout) throws IOException, GitAPIException { try (Git git = Git.open(this.repositoryPathAsFile)) { diff --git a/src/main/java/org/jabref/logic/git/SlrGitHandler.java b/src/main/java/org/jabref/logic/git/SlrGitHandler.java index 334f4c7feec..cd61520ae03 100644 --- a/src/main/java/org/jabref/logic/git/SlrGitHandler.java +++ b/src/main/java/org/jabref/logic/git/SlrGitHandler.java @@ -25,6 +25,7 @@ import org.eclipse.jgit.treewalk.CanonicalTreeParser; public class SlrGitHandler extends GitHandler { + /** * Initialize the handler for the given repository * diff --git a/src/main/java/org/jabref/logic/util/io/FileUtil.java b/src/main/java/org/jabref/logic/util/io/FileUtil.java index d92e242afea..7d63235c647 100644 --- a/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -89,10 +89,10 @@ public static String getBaseName(Path fileNameWithExtension) { /** * Returns a valid filename for most operating systems. - *

+ * * Currently, only the length is restricted to 255 chars, see MAXIMUM_FILE_NAME_LENGTH. * - * See also {@link FileHelper#detectBadFileName(String)} and {@link FileNameCleaner#cleanFileName(String)} + * For "real" cleaning, {@link FileNameCleaner#cleanFileName(String)} should be used. */ public static String getValidFileName(String fileName) { String nameWithoutExtension = getBaseName(fileName); @@ -390,7 +390,7 @@ public static boolean isPDFFile(Path file) { } /** - * @return Path of current panel database directory or the standard working directory in case the datbase was not saved yet + * @return Path of current panel database directory or the standard working directory in case the database was not saved yet */ public static Path getInitialDirectory(BibDatabaseContext databaseContext, Path workingDirectory) { return databaseContext.getDatabasePath().map(Path::getParent).orElse(workingDirectory); diff --git a/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/src/main/java/org/jabref/model/database/BibDatabaseContext.java index 299716792e6..45563f6328a 100644 --- a/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -10,6 +10,8 @@ import org.jabref.architecture.AllowedToUseLogic; import org.jabref.gui.LibraryTab; +import org.jabref.logic.crawler.Crawler; +import org.jabref.logic.crawler.StudyRepository; import org.jabref.logic.shared.DatabaseLocation; import org.jabref.logic.shared.DatabaseSynchronizer; import org.jabref.logic.util.CoarseChangeFilter; @@ -17,6 +19,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.metadata.MetaData; import org.jabref.model.pdf.search.SearchFieldConstants; +import org.jabref.model.study.Study; import org.jabref.preferences.FilePreferences; import net.harawata.appdirs.AppDirsFactory; @@ -113,6 +116,16 @@ public boolean isBiblatexMode() { return getMode() == BibDatabaseMode.BIBLATEX; } + /** + * Returns whether this .bib file belongs to a {@link Study} + */ + public boolean isStudy() { + return this.getDatabasePath() + .map(path -> path.getFileName().toString().equals(Crawler.FILENAME_STUDY_RESULT_BIB) && + Files.exists(path.resolveSibling(StudyRepository.STUDY_DEFINITION_FILE_NAME))) + .orElse(false); + } + /** * Look up the directories set up for this database. * There can be up to four directories definitions for these files: diff --git a/src/main/java/org/jabref/model/study/Study.java b/src/main/java/org/jabref/model/study/Study.java index c9a416eda4e..5f0b1648f7f 100644 --- a/src/main/java/org/jabref/model/study/Study.java +++ b/src/main/java/org/jabref/model/study/Study.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.Objects; +import org.jabref.logic.crawler.StudyYamlParser; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -11,6 +13,8 @@ * This class represents a scientific study. * * This class defines all aspects of a scientific study relevant to the application. It is a proxy for the file based study definition. + * + * The file is parsed using by {@link StudyYamlParser} */ @JsonPropertyOrder({"authors", "title", "research-questions", "queries", "databases"}) // The user might add arbitrary content to the YAML diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index ef7f129a320..496e4face75 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -2171,11 +2171,11 @@ public FieldWriterPreferences getFieldWriterPreferences() { * @param originalDirectory the directory as configured */ private String determineMainFileDirectory(String originalDirectory) { - if (!originalDirectory.isEmpty()) { + if ((originalDirectory != null) && !originalDirectory.isEmpty()) { // A non-empty directory is kept return originalDirectory; } - return Path.of(JabRefDesktop.getDefaultFileChooserDirectory(), "JabRef").toString(); + return JabRefDesktop.getDefaultFileChooserDirectory().toString(); } @Override diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index c560fc1b280..c9e469a00f8 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -69,8 +69,6 @@ All\ entries=All entries Also\ remove\ subgroups=Also remove subgroups -Always\ reformat\ BIB\ file\ on\ save\ and\ export=Always reformat BIB file on save and export - and=and any\ field\ that\ matches\ the\ regular\ expression\ %0=any field that matches the regular expression %0 @@ -198,9 +196,6 @@ Could\ not\ print\ preview=Could not print preview Could\ not\ run\ the\ 'vim'\ program.=Could not run the 'vim' program. -Could\ not\ save\ file.=Could not save file. -Character\ encoding\ '%0'\ is\ not\ supported.=Character encoding '%0' is not supported. - Create\ custom\ fields\ for\ each\ BibTeX\ entry=Create custom fields for each BibTeX entry crossreferenced\ entries\ included=crossreferenced entries included @@ -366,7 +361,10 @@ Filter=Filter Filter\ groups=Filter groups +Success\!\ Finished\ writing\ metadata.=Success! Finished writing metadata. Finished\ writing\ metadata\ for\ %0\ file\ (%1\ skipped,\ %2\ errors).=Finished writing metadata for %0 file (%1 skipped, %2 errors). +Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Error while writing metadata. See the error log for details. +Failed\ to\ write\ metadata,\ file\ %1\ not\ found.=Failed to write metadata, file %1 not found. First\ select\ the\ entries\ you\ want\ keys\ to\ be\ generated\ for.=First select the entries you want keys to be generated for. @@ -383,8 +381,6 @@ Formatter\ name=Formatter name found\ in\ AUX\ file=found in AUX file -Fulltext\ search=Fulltext search - Fulltext\ for=Fulltext for Further\ information\ about\ Mr.\ DLib\ for\ JabRef\ users.=Further information about Mr. DLib for JabRef users. @@ -414,7 +410,6 @@ Have\ you\ chosen\ the\ correct\ package\ path?=Have you chosen the correct pack Help=Help Help\ on\ key\ patterns=Help on key patterns -Help\ on\ regular\ expression\ search=Help on regular expression search Hide\ non-hits=Hide non-hits @@ -769,26 +764,71 @@ Loading=Loading Save=Save Save\ all\ finished.=Save all finished. - Save\ all\ open\ libraries=Save all open libraries - Save\ before\ closing=Save before closing - Save\ library=Save library Save\ library\ as...=Save library as... Saving=Saving Saving\ all\ libraries...=Saving all libraries... Saving\ library=Saving library Library\ saved=Library saved +Could\ not\ save\ file.=Could not save file. +Could\ not\ save,\ file\ locked\ by\ another\ JabRef\ instance.=Could not save, file locked by another JabRef instance. Saved\ selected\ to\ '%0'.=Saved selected to '%0'. +Autosave\ local\ libraries=Autosave local libraries +Automatically\ save\ the\ library\ to=Automatically save the library to +Please\ enter\ a\ valid\ file\ path.=Please enter a valid file path. +Overwrite\ file=Overwrite file +Unable\ to\ write\ to\ %0.=Unable to write to %0. -Search=Search +Refuse\ to\ save\ the\ library\ before\ external\ changes\ have\ been\ reviewed.=Refuse to save the library before external changes have been reviewed. +Library\ protection=Library protection +Unable\ to\ save\ library=Unable to save library -Search\ expression=Search expression +Always\ reformat\ BIB\ file\ on\ save\ and\ export=Always reformat BIB file on save and export +Character\ encoding\ '%0'\ is\ not\ supported.=Character encoding '%0' is not supported. +Search=Search +Searching...=Searching... +Finished\ Searching=Finished Searching +Search\ expression=Search expression +Fulltext\ search=Fulltext search +Help\ on\ regular\ expression\ search=Help on regular expression search Searching\ for\ duplicates...=Searching for duplicates... - Searching\ for\ files=Searching for files +The\ search\ is\ case-insensitive.=The search is case-insensitive. +The\ search\ is\ case-sensitive.=The search is case-sensitive. +Use\ regular\ expression\ search=Use regular expression search +search\ expression=search expression +Free\ search\ expression=Free search expression +Search\ failed\:\ illegal\ search\ expression=Search failed: illegal search expression +No\ search\ matches.=No search matches. +Web\ search=Web search +Search\ results=Search results +Please\ enter\ a\ search\ string=Please enter a search string +Please\ open\ or\ start\ a\ new\ library\ before\ searching=Please open or start a new library before searching +Please\ enter\ a\ field\ name\ to\ search\ for\ a\ keyword.=Please enter a field name to search for a keyword. +No\ results\ found.=No results found. +Found\ %0\ results.=Found %0 results. +Invalid\ regular\ expression=Invalid regular expression +This\ search\ contains\ entries\ in\ which\ any\ field\ contains\ the\ regular\ expression\ %0=This search contains entries in which any field contains the regular expression %0 +This\ search\ contains\ entries\ in\ which\ any\ field\ contains\ the\ term\ %0=This search contains entries in which any field contains the term %0 +This\ search\ contains\ entries\ in\ which=This search contains entries in which +Empty\ search\ ID=Empty search ID +The\ given\ search\ ID\ was\ empty.=The given search ID was empty. +Clear\ search=Clear search +Search\ document\ identifier\ online=Search document identifier online +Search\ for\ unlinked\ local\ files=Search for unlinked local files +Search\ full\ text\ documents\ online=Search full text documents online +Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\=Smith\ and\ title\=electrical=Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor=Smith and title=electrical +Search\ term\ is\ empty.=Search term is empty. +Invalid\ regular\ expression.=Invalid regular expression. +Searching\ for\ a\ keyword=Searching for a keyword + +Search\ across\ libraries\ in\ a\ new\ window=Search across libraries in a new window +Keep\ search\ string\ across\ libraries=Keep search string across libraries +Keep\ dialog\ always\ on\ top=Keep dialog always on top +Search\ results\ from\ open\ libraries=Search results from open libraries Select\ all=Select all Select\ new\ encoding=Select new encoding @@ -849,7 +889,6 @@ Sublibrary\ from\ AUX\ to\ BibTeX=Sublibrary from AUX to BibTeX Switches\ between\ full\ and\ abbreviated\ journal\ name\ if\ the\ journal\ name\ is\ known.=Switches between full and abbreviated journal name if the journal name is known. - the\ field\ %0=the field %0 The\ group\ "%0"\ already\ contains\ the\ selection.=The group "%0" already contains the selection. @@ -861,10 +900,6 @@ The\ label\ of\ the\ string\ cannot\ contain\ the\ '\#'\ character.=The label of The\ output\ option\ depends\ on\ a\ valid\ import\ option.=The output option depends on a valid import option. -The\ search\ is\ case\ insensitive.=The search is case insensitive. - -The\ search\ is\ case\ sensitive.=The search is case sensitive. - There\ are\ possible\ duplicates\ that\ haven't\ been\ resolved.\ Continue?=There are possible duplicates that haven't been resolved. Continue? This\ operation\ requires\ all\ selected\ entries\ to\ have\ citation\ keys\ defined.=This operation requires all selected entries to have citation keys defined. @@ -885,8 +920,6 @@ Try\ different\ encoding=Try different encoding Unabbreviate\ journal\ names\ of\ the\ selected\ entries=Unabbreviate journal names of the selected entries Unabbreviated\ %0\ journal\ names.=Unabbreviated %0 journal names. -unable\ to\ write\ to=unable to write to - Undo=Undo Unknown\ BibTeX\ entries\:=Unknown BibTeX entries\: @@ -902,7 +935,6 @@ Upgrade\ external\ PDF/PS\ links\ to\ use\ the\ '%0'\ field.=Upgrade external PD usage=usage Use\ autocompletion=Use autocompletion -Use\ regular\ expression\ search=Use regular expression search Username=Username @@ -910,7 +942,7 @@ Value\ cleared\ externally=Value cleared externally Value\ set\ externally\:\ %0=Value set externally: %0 -verify\ that\ LyX\ is\ running\ and\ that\ the\ lyxpipe\ is\ valid=verify that LyX is running and that the lyxpipe is valid +Verify\ that\ LyX\ is\ running\ and\ that\ the\ lyxpipe\ is\ valid.=Verify that LyX is running and that the lyxpipe is valid. View=View Vim\ server\ name=Vim server name @@ -964,10 +996,6 @@ Could\ not\ find\ file\ '%0'.=Could not find file '%0'. Number\ of\ entries\ successfully\ imported=Number of entries successfully imported Error\ while\ fetching\ from\ %0=Error while fetching from %0 -Refuse\ to\ save\ the\ library\ before\ external\ changes\ have\ been\ reviewed.=Refuse to save the library before external changes have been reviewed. -Library\ protection=Library protection -Unable\ to\ save\ library=Unable to save library - Citation\ key\ generator=Citation key generator Unable\ to\ open\ link.=Unable to open link. MIME\ type=MIME type @@ -986,14 +1014,10 @@ Cannot\ use\ port\ %0\ for\ remote\ operation;\ another\ application\ may\ be\ u Looking\ for\ full\ text\ document...=Looking for full text document... A\ local\ copy\ will\ be\ opened.=A local copy will be opened. -Autosave\ local\ libraries=Autosave local libraries -Automatically\ save\ the\ library\ to=Automatically save the library to -Please\ enter\ a\ valid\ file\ path.=Please enter a valid file path. Error\ opening\ file\ '%0'.=Error opening file '%0'. Formatter\ not\ found\:\ %0=Formatter not found: %0 -Could\ not\ save,\ file\ locked\ by\ another\ JabRef\ instance.=Could not save, file locked by another JabRef instance. Metadata\ change=Metadata change The\ following\ metadata\ changed\:=The following metadata changed: @@ -1005,7 +1029,6 @@ dynamic\ group=dynamic group refines\ supergroup=refines supergroup includes\ subgroups=includes subgroups contains=contains -search\ expression=search expression Optional\ fields\ 2=Optional fields 2 Waiting\ for\ save\ operation\ to\ finish=Waiting for save operation to finish @@ -1019,7 +1042,6 @@ Duplicate\ citation\ key=Duplicate citation key General\ file\ directory=General file directory User-specific\ file\ directory=User-specific file directory LaTeX\ file\ directory=LaTeX file directory -Search\ failed\:\ illegal\ search\ expression=Search failed: illegal search expression You\ must\ enter\ an\ integer\ value\ in\ the\ interval\ 1025-65535=You must enter an integer value in the interval 1025-65535 Autocomplete\ names\ in\ 'Firstname\ Lastname'\ format\ only=Autocomplete names in 'Firstname Lastname' format only @@ -1039,7 +1061,6 @@ Use\ custom\ file\ browser=Use custom file browser Use\ custom\ terminal\ emulator=Use custom terminal emulator exportFormat=exportFormat Output\ file\ missing=Output file missing -No\ search\ matches.=No search matches. The\ output\ option\ depends\ on\ a\ valid\ input\ option.=The output option depends on a valid input option. Linked\ file\ name\ conventions=Linked file name conventions Filename\ format\ pattern=Filename format pattern @@ -1107,7 +1128,6 @@ HTML\ list=HTML list If\ possible,\ normalize\ this\ list\ of\ names\ to\ conform\ to\ standard\ BibTeX\ name\ formatting=If possible, normalize this list of names to conform to standard BibTeX name formatting Could\ not\ open\ %0=Could not open %0 Unknown\ import\ format=Unknown import format -Web\ search=Web search Style\ selection=Style selection No\ valid\ style\ file\ defined=No valid style file defined Choose\ pattern=Choose pattern @@ -1162,7 +1182,6 @@ Sort\ by\:=Sort by: Newest\ first=Newest first Oldest\ first=Oldest first Directory=Directory -Search\ results=Search results Import\ result=Import result Searching\ file\ system...=Searching file system... Citation\ key\ patterns=Citation key patterns @@ -1212,10 +1231,6 @@ Unable\ to\ connect.\ One\ possible\ reason\ is\ that\ JabRef\ and\ OpenOffice/L Delimiter(s)=Delimiter(s) When\ downloading\ files,\ or\ moving\ linked\ files\ to\ the\ file\ directory,\ use\ the\ bib\ file\ location.=When downloading files, or moving linked files to the file directory, use the bib file location. -Searching...=Searching... -Please\ enter\ a\ search\ string=Please enter a search string -Please\ open\ or\ start\ a\ new\ library\ before\ searching=Please open or start a new library before searching - Canceled\ merging\ entries=Canceled merging entries Merge\ entries=Merge entries @@ -1352,12 +1367,6 @@ unexpected\ opening\ curly\ bracket=unexpected opening curly bracket capital\ letters\ are\ not\ masked\ using\ curly\ brackets\ {}=capital letters are not masked using curly brackets {} should\ contain\ a\ four\ digit\ number=should contain a four digit number should\ contain\ a\ valid\ page\ number\ range=should contain a valid page number range -No\ results\ found.=No results found. -Found\ %0\ results.=Found %0 results. -Invalid\ regular\ expression=Invalid regular expression -This\ search\ contains\ entries\ in\ which\ any\ field\ contains\ the\ regular\ expression\ %0=This search contains entries in which any field contains the regular expression %0 -This\ search\ contains\ entries\ in\ which\ any\ field\ contains\ the\ term\ %0=This search contains entries in which any field contains the term %0 -This\ search\ contains\ entries\ in\ which=This search contains entries in which Unable\ to\ autodetect\ OpenOffice/LibreOffice\ installation.\ Please\ choose\ the\ installation\ directory\ manually.=Unable to autodetect OpenOffice/LibreOffice installation. Please choose the installation directory manually. @@ -1761,8 +1770,6 @@ Invalid\ ISBN\:\ '%0'.=Invalid ISBN: '%0'. should\ be\ an\ integer\ or\ normalized=should be an integer or normalized should\ be\ normalized=should be normalized -Empty\ search\ ID=Empty search ID -The\ given\ search\ ID\ was\ empty.=The given search ID was empty. biblatex\ field\ only=biblatex field only Error\ while\ generating\ fetch\ URL=Error while generating fetch URL @@ -1875,7 +1882,6 @@ Keep\ entry=Keep entry Ignore\ backup=Ignore backup Restore\ from\ backup=Restore from backup -Overwrite\ file=Overwrite file Shared\ database\ connection=Shared database connection Could\ not\ connect\ to\ Vim\ server.\ Make\ sure\ that\ Vim\ is\ running\ with\ correct\ server\ name.=Could not connect to Vim server. Make sure that Vim is running with correct server name. @@ -1923,7 +1929,6 @@ View\ event\ log=View event log Website=Website Override\ default\ font\ settings=Override default font settings -Clear\ search=Clear search Click\ help\ to\ learn\ about\ the\ migration\ of\ pre-3.6\ databases.=Click help to learn about the migration of pre-3.6 databases. Database\ Type\:=Database Type\: @@ -1980,9 +1985,6 @@ New\ library=New library OpenOffice/LibreOffice=OpenOffice/LibreOffice Open\ document\ viewer=Open document viewer Open\ entry\ editor=Open entry editor -Search\ document\ identifier\ online=Search document identifier online -Search\ for\ unlinked\ local\ files=Search for unlinked local files -Search\ full\ text\ documents\ online=Search full text documents online Find\ and\ replace=Find and replace Found\ documents\:=Found documents\: @@ -2082,20 +2084,14 @@ Intersection=Intersection Union=Union Collect\ by=Collect by Explicit\ selection=Explicit selection -Searching\ for\ a\ keyword=Searching for a keyword -Free\ search\ expression=Free search expression Specified\ keywords=Specified keywords Cited\ entries=Cited entries -Search\ term\ is\ empty.=Search term is empty. -Invalid\ regular\ expression.=Invalid regular expression. Please\ provide\ a\ valid\ aux\ file.=Please provide a valid aux file. Keyword\ delimiter=Keyword delimiter Hierarchical\ keyword\ delimiter=Hierarchical keyword delimiter Escape\ ampersands=Escape ampersands Escape\ dollar\ sign=Escape dollar sign -Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\=Smith\ and\ title\=electrical=Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor=Smith and title=electrical - Copied\ '%0'\ to\ clipboard.=Copied '%0' to clipboard. This\ operation\ requires\ an\ open\ library.=This operation requires an open library. @@ -2149,7 +2145,6 @@ Unable\ to\ open\ ShortScience.=Unable to open ShortScience. Shared\ database=Shared database Lookup=Lookup -Please\ enter\ a\ field\ name\ to\ search\ for\ a\ keyword.=Please enter a field name to search for a keyword. Access\ date\ of\ the\ address\ specified\ in\ the\ url\ field.=Access date of the address specified in the url field. Additional\ information\ related\ to\ the\ resource\ indicated\ by\ the\ eprint\ field.=Additional information related to the resource indicated by the eprint field. Annex\ to\ the\ eventtitle\ field.=Annex to the eventtitle field. @@ -2404,16 +2399,15 @@ Recommended=Recommended Authors\ and\ Title=Authors and Title Database=Database Databases=Databases -Manage\ study\ definition=Manage study definition Add\ Author\:=Add Author\: Add\ Query\:=Add Query\: Add\ Research\ Question\:=Add Research Question\: -Perform\ search\ for\ existing\ systematic\ literature\ review=Perform search for existing systematic literature review Queries=Queries Research\ Questions=Research Questions -Searching=Searching -Start\ new\ systematic\ literature\ review=Start new systematic literature review Study\ Title\:=Study Title\: +Start\ new\ systematic\ literature\ review=Start new systematic literature review +Manage\ study\ definition=Manage study definition +Update\ study\ search\ results=Update study search results Study\ repository\ could\ not\ be\ created=Study repository could not be created Select\ Databases\:=Select Databases: @@ -2423,7 +2417,6 @@ If\ the\ sequence\ of\ terms\ is\ relevant\ wrap\ them\ in\ double\ quotes =If t Query\ terms\ are\ separated\ by\ spaces.=Query terms are separated by spaces. Select\ the\ study\ directory\:=Select the study directory\: An\ example\:=An example\: -Define\ study\ parameters=Define study parameters Start\ survey=Start survey Query=Query Question=Question @@ -2456,11 +2449,6 @@ Verbatim=Verbatim Word\ by\ word=Word by word Could\ not\ extract\ Metadata\ from\:\ %0=Could not extract Metadata from: %0 -Search\ across\ libraries\ in\ a\ new\ window=Search across libraries in a new window -Keep\ search\ string\ across\ libraries=Keep search string across libraries -Keep\ dialog\ always\ on\ top=Keep dialog always on top -Search\ results\ from\ open\ libraries=Search results from open libraries - Add\ certificate=Add certificate Serial\ number=Serial number Issuer=Issuer @@ -2480,10 +2468,6 @@ Bibliographic\ data\ not\ found.\ Cause\ is\ likely\ the\ server\ side.\ Please\ Error\ message\ %0=Error message %0 Identifier\ not\ found=Identifier not found -Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Error while writing metadata. See the error log for details. -Failed\ to\ write\ metadata,\ file\ %1\ not\ found.=Failed to write metadata, file %1 not found. -Success\!\ Finished\ writing\ metadata.=Success! Finished writing metadata. - Custom\ API\ key=Custom API key Check\ %0\ API\ Key\ Setting=Check %0 API Key Setting diff --git a/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java b/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java index cad5aa79e89..ce2c4cc3a49 100644 --- a/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java +++ b/src/test/java/org/jabref/gui/search/GrammarBasedSearchRuleDescriberTest.java @@ -44,7 +44,7 @@ void testSimpleQueryCaseSensitiveRegex() { String query = "a=b"; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), - TooltipTextUtil.createText("The search is case sensitive.")); + TooltipTextUtil.createText("The search is case-sensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -55,7 +55,7 @@ void testSimpleQueryCaseSensitive() { String query = "a=b"; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), - TooltipTextUtil.createText("The search is case sensitive.")); + TooltipTextUtil.createText("The search is case-sensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -66,7 +66,7 @@ void testSimpleQuery() { String query = "a=b"; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), - TooltipTextUtil.createText("The search is case insensitive.")); + TooltipTextUtil.createText("The search is case-insensitive.")); TextFlow description = createDescription(query, EnumSet.noneOf(SearchFlags.class)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -77,7 +77,7 @@ void testSimpleQueryRegex() { String query = "a=b"; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), - TooltipTextUtil.createText("The search is case insensitive.")); + TooltipTextUtil.createText("The search is case-insensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -89,7 +89,7 @@ void testComplexQueryCaseSensitiveRegex() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), - TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); + TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case-sensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -101,7 +101,7 @@ void testComplexQueryRegex() { List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the regular expression "), - TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); + TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case-insensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -112,7 +112,7 @@ void testComplexQueryCaseSensitive() { String query = "not a=b and c=e or e=\"x\""; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), - TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case sensitive.")); + TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case-sensitive.")); TextFlow description = createDescription(query, EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); @@ -123,7 +123,7 @@ void testComplexQuery() { String query = "not a=b and c=e or e=\"x\""; List expectedTexts = Arrays.asList(TooltipTextUtil.createText("This search contains entries in which "), TooltipTextUtil.createText("not "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("a", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("b", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" and "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("c", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), - TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case insensitive.")); + TooltipTextUtil.createText(" or "), TooltipTextUtil.createText("the field "), TooltipTextUtil.createText("e", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(" contains the term "), TooltipTextUtil.createText("x", TooltipTextUtil.TextType.BOLD), TooltipTextUtil.createText(". "), TooltipTextUtil.createText("The search is case-insensitive.")); TextFlow description = createDescription(query, EnumSet.noneOf(SearchFlags.class)); TextFlowEqualityHelper.assertEquals(expectedTexts, description); diff --git a/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java b/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java index f6f9d927909..c5ac3c8ee58 100644 --- a/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java +++ b/src/test/java/org/jabref/gui/slr/ManageStudyDefinitionViewModelTest.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.util.List; +import org.jabref.gui.DialogService; import org.jabref.logic.bibtex.FieldContentFormatterPreferences; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.ImporterPreferences; @@ -21,6 +22,7 @@ class ManageStudyDefinitionViewModelTest { private ImportFormatPreferences importFormatPreferences; private ImporterPreferences importerPreferences; + private DialogService dialogService; @BeforeEach void setUp() { @@ -29,11 +31,12 @@ void setUp() { importerPreferences = mock(ImporterPreferences.class); FieldContentFormatterPreferences fieldContentFormatterPreferences = mock(FieldContentFormatterPreferences.class); when(importFormatPreferences.getFieldContentFormatterPreferences()).thenReturn(fieldContentFormatterPreferences); + dialogService = mock(DialogService.class); } @Test public void emptyStudyConstructorFillsDatabasesCorrectly() { - ManageStudyDefinitionViewModel manageStudyDefinitionViewModel = new ManageStudyDefinitionViewModel(importFormatPreferences, importerPreferences); + ManageStudyDefinitionViewModel manageStudyDefinitionViewModel = new ManageStudyDefinitionViewModel(importFormatPreferences, importerPreferences, dialogService); assertEquals(List.of( new StudyDatabaseItem("ACM Portal", true), new StudyDatabaseItem("ArXiv", false), @@ -72,7 +75,8 @@ public void studyConstructorFillsDatabasesCorrectly(@TempDir Path tempDir) { study, tempDir, importFormatPreferences, - importerPreferences); + importerPreferences, + dialogService); assertEquals(List.of( new StudyDatabaseItem("ACM Portal", true), new StudyDatabaseItem("ArXiv", false), diff --git a/src/test/java/org/jabref/logic/git/GitHandlerTest.java b/src/test/java/org/jabref/logic/git/GitHandlerTest.java index 6f5e30052cf..d5dd4c694c3 100644 --- a/src/test/java/org/jabref/logic/git/GitHandlerTest.java +++ b/src/test/java/org/jabref/logic/git/GitHandlerTest.java @@ -53,6 +53,6 @@ void createCommitOnCurrentBranch() throws IOException, GitAPIException { @Test void getCurrentlyCheckedOutBranch() throws IOException { - assertEquals("master", gitHandler.getCurrentlyCheckedOutBranch()); + assertEquals("main", gitHandler.getCurrentlyCheckedOutBranch()); } }