Add export and import function for the model lists (recording and later)

This commit is contained in:
0xb00bface 2021-12-20 15:57:50 +01:00
parent 0d512134ed
commit f04eb5310e
15 changed files with 467 additions and 105 deletions

View File

@ -2,6 +2,7 @@ package ctbrec.ui.controls;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -29,17 +30,19 @@ public abstract class AbstractFileSelectionBox extends HBox {
private static final Logger LOG = LoggerFactory.getLogger(AbstractFileSelectionBox.class);
private StringProperty fileProperty = new SimpleStringProperty();
private final StringProperty fileProperty = new SimpleStringProperty();
private final Tooltip validationError = new Tooltip();
protected TextField fileInput;
protected boolean allowEmptyValue = false;
private Tooltip validationError = new Tooltip();
protected boolean saveDialog = false;
protected boolean validationDisabled = false;
protected AbstractFileSelectionBox() {
super(5);
fileInput = new TextField();
fileInput.textProperty().addListener(textListener());
fileInput.focusedProperty().addListener((obs, o, n) -> {
if (!n.booleanValue()) {
if (Objects.equals(Boolean.FALSE, n)) {
validationError.hide();
}
});
@ -52,7 +55,7 @@ public abstract class AbstractFileSelectionBox extends HBox {
HBox.setHgrow(fileInput, Priority.ALWAYS);
disabledProperty().addListener((obs, oldV, newV) -> {
if (newV.booleanValue()) {
if (Objects.equals(Boolean.TRUE, newV)) {
hideValidationHints();
} else {
if (StringUtil.isNotBlank(fileInput.getText())) {
@ -74,7 +77,6 @@ public abstract class AbstractFileSelectionBox extends HBox {
if (allowEmptyValue) {
fileProperty.set("");
hideValidationHints();
return;
}
} else {
var program = new File(input);
@ -106,7 +108,7 @@ public abstract class AbstractFileSelectionBox extends HBox {
}
protected String validate(File file) {
if (isDisabled()) {
if (isDisabled() || validationDisabled) {
return null;
}
@ -121,6 +123,14 @@ public abstract class AbstractFileSelectionBox extends HBox {
this.allowEmptyValue = true;
}
public void useSaveDialog() {
this.saveDialog = true;
}
public void disableValidation() {
validationDisabled = true;
}
private Button createBrowseButton() {
var button = new Button("Select");
button.setOnAction(e -> choose());
@ -131,7 +141,12 @@ public abstract class AbstractFileSelectionBox extends HBox {
protected void choose() {
var chooser = new FileChooser();
var program = chooser.showOpenDialog(null);
File program;
if (saveDialog) {
program = chooser.showSaveDialog(null);
} else {
program = chooser.showOpenDialog(null);
}
if (program != null) {
try {
fileInput.setText(program.getCanonicalPath());

View File

@ -1,37 +1,23 @@
package ctbrec.ui.controls;
import static javafx.scene.control.ButtonType.*;
import java.io.InputStream;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.StringUtil;
import ctbrec.ui.AutosizeAlert;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Region;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.InputStream;
import java.util.Optional;
import static javafx.scene.control.ButtonType.*;
public class Dialogs {
private Dialogs() {}
@ -149,36 +135,4 @@ public class Dialogs {
confirm.showAndWait();
return confirm.getResult();
}
public static Optional<ModelGroup> showModelGroupSelectionDialog(Scene parent, Model model) {
var dialogPane = new GridPane();
Set<ModelGroup> modelGroups = Config.getInstance().getSettings().modelGroups;
ObservableList<ModelGroup> comboBoxModel = FXCollections.observableArrayList(modelGroups);
ComboBox<ModelGroup> comboBox = new ComboBox<>(comboBoxModel);
comboBox.setEditable(true);
comboBox.setPlaceholder(new Label(" type in a name to a add a new group "));
dialogPane.add(new Label("Model group"), 0, 0);
dialogPane.add(comboBox, 1, 0);
boolean ok = showCustomInput(parent, "Add model to group", dialogPane);
if (ok) {
String text = comboBox.getEditor().getText();
if (StringUtil.isBlank(text)) {
return Optional.empty();
}
Optional<ModelGroup> existingGroup = modelGroups.stream().filter(mg -> mg.getName().equalsIgnoreCase(text)).findFirst();
if (existingGroup.isPresent()) {
existingGroup.get().add(model);
return existingGroup;
} else {
var group = new ModelGroup();
group.setId(UUID.randomUUID());
group.setName(text);
group.add(model);
modelGroups.add(group);
return Optional.of(group);
}
} else {
return Optional.empty();
}
}
}

View File

@ -12,7 +12,7 @@ public class FileSelectionBox extends AbstractFileSelectionBox {
@Override
protected String validate(File file) {
if (isDisabled()) {
if (isDisabled() || validationDisabled) {
return null;
}

View File

@ -28,39 +28,43 @@ import ctbrec.ui.controls.table.SettingTableViewStateStore;
import ctbrec.ui.controls.table.StatePersistingTableView;
import ctbrec.ui.menu.ModelMenuContributor;
import ctbrec.ui.tabs.TabSelectionListener;
import ctbrec.ui.tabs.recorded.ModelImportExport.ExportOptions;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringPropertyBase;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.FileChooser;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import static ctbrec.ui.action.AbstractPortraitAction.FORMAT;
import static ctbrec.ui.tabs.recorded.ModelImportExport.ExportIncludes.*;
public abstract class AbstractRecordedModelsTab extends Tab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(AbstractRecordedModelsTab.class);
@ -77,7 +81,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
protected LoadingCache<Model, Image> portraitCache = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.DAYS)
.maximumSize(1000)
.build(CacheLoader.from(AbstractRecordedModelsTab::loadModelPortrait));
.build(CacheLoader.from(this::loadModelPortrait));
protected AutoFillTextField modelInputField;
protected List<Site> sites;
@ -88,6 +92,8 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
protected Label modelLabel = new Label("Model");
protected Button addModelButton = new Button("Record");
protected Button checkModelAccountExistance = new Button("Check URLs");
protected Button exportModelsButton = new Button();
protected Button importModelsButton = new Button();
protected TextField filter;
protected FlowPane grid = new FlowPane();
@ -95,10 +101,12 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
protected ContextMenu popup;
protected Config config;
protected PortraitStore portraitStore;
AbstractRecordedModelsTab(String text, String stateStorePrefix) {
super(text);
config = Config.getInstance();
portraitStore = new PortraitStore(config);
tableStateStore = new SettingTableViewStateStore(config, stateStorePrefix);
table = new StatePersistingTableView<>(tableStateStore);
registerPortraitListener();
@ -178,7 +186,20 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
BorderPane.setMargin(addModelBox, new Insets(5));
addModelButton.setOnAction(this::addModel);
addModelButton.setPadding(new Insets(5));
addModelBox.getChildren().addAll(modelLabel, modelInputField, addModelButton, checkModelAccountExistance);
ImageView exportIcon = new ImageView(Objects.requireNonNull(getClass().getResource("/16/download.png"), "/16/download.png not found").toString());
exportModelsButton.setGraphic(exportIcon);
exportModelsButton.setTooltip(new Tooltip("Export models to file"));
exportModelsButton.setMinWidth(34);
exportModelsButton.setMinHeight(26);
exportModelsButton.setOnAction(this::exportModels);
ImageView importIcon = new ImageView(Objects.requireNonNull(getClass().getResource("/16/upload.png"), "/16/upload.png not found").toString());
importModelsButton.setGraphic(importIcon);
importModelsButton.setTooltip(new Tooltip("Import models from file"));
importModelsButton.setMinWidth(34);
importModelsButton.setMinHeight(26);
importModelsButton.setOnAction(this::importModels);
HBox.setMargin(exportModelsButton, new Insets(0, 0, 0, 20));
addModelBox.getChildren().addAll(modelLabel, modelInputField, addModelButton, checkModelAccountExistance, exportModelsButton, importModelsButton);
filterContainer.setPadding(new Insets(0));
filterContainer.setAlignment(Pos.CENTER_RIGHT);
@ -204,6 +225,59 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
addModelBox.getChildren().add(filterContainer);
}
protected abstract List<Model> getExportList();
private void exportModels(ActionEvent actionEvent) {
ExportOptions exportOptions = new ModelExportDialog(getTabPane()).showAndWait();
if (exportOptions != null) {
try {
ModelImportExport.exportTo(getExportList(), config, exportOptions);
} catch (IOException e) {
String msg = "An error occurred while exporting the model list";
Dialogs.showError(getTabPane().getScene(), "Export models", msg, e);
LOG.error(msg, e);
}
}
}
protected void importModelList(List<Model> models) {
getContent().setCursor(Cursor.WAIT);
Task<Void> task = new Task<>() {
@Override
protected Void call() {
for (Model model : models) {
try {
recorder.addModel(model);
} catch (Exception e) {
LOG.error("Couldn't add model to recording list", e);
}
}
return null;
}
@Override
protected void done() {
getContent().setCursor(Cursor.DEFAULT);
}
};
GlobalThreadPool.submit(task);
}
private void importModels(ActionEvent actionEvent) {
var chooser = new FileChooser();
File target = chooser.showOpenDialog(getTabPane().getScene().getWindow());
if (target != null) {
try {
List<Model> models = ModelImportExport.importFrom(target, sites, config);
importModelList(models);
} catch (IOException e) {
String msg = "An error occurred while importing the model list";
Dialogs.showError(getTabPane().getScene(), "Import models", msg, e);
LOG.error(msg, e);
}
}
}
protected void addPreviewColumn(int columnIdx) {
TableColumn<JavaFxModel, String> preview = addTableColumn("preview", "🎥", columnIdx, 35);
preview.setCellValueFactory(cdf -> new SimpleStringProperty(""));
@ -292,7 +366,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
}
protected ContextMenu createContextMenu() {
List<Model> selectedModels = table.getSelectionModel().getSelectedItems().stream().map(JavaFxModel::getDelegate).collect(Collectors.toList());
List<Model> selectedModels = table.getSelectionModel().getSelectedItems().stream().map(JavaFxModel::getDelegate).toList();
if (selectedModels.isEmpty()) {
return null;
}
@ -487,18 +561,7 @@ public abstract class AbstractRecordedModelsTab extends Tab implements TabSelect
}
}
protected static Image loadModelPortrait(Model model) {
String portraitId = Config.getInstance().getSettings().modelPortraits.get(model.getUrl());
if (StringUtil.isNotBlank(portraitId)) {
File configDir = Config.getInstance().getConfigDir();
File portraitDir = new File(configDir, "portraits");
File portraitFile = new File(portraitDir, portraitId + '.' + FORMAT);
try {
return new Image(new FileInputStream(portraitFile));
} catch (FileNotFoundException e) {
LOG.error("Couldn't load portrait file {}", portraitFile, e);
}
}
return SILHOUETTE;
protected Image loadModelPortrait(Model model) {
return portraitStore.loadModelPortrait(model.getUrl()).orElse(SILHOUETTE);
}
}

View File

@ -0,0 +1,84 @@
package ctbrec.ui.tabs.recorded;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.FileSelectionBox;
import ctbrec.ui.tabs.recorded.ModelImportExport.ExportIncludes;
import ctbrec.ui.tabs.recorded.ModelImportExport.ExportOptions;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import static ctbrec.ui.tabs.recorded.ModelImportExport.ExportIncludes.*;
public class ModelExportDialog {
private final Node source;
private final GridPane gridPane = new GridPane();
private CheckBox notesButton;
private CheckBox groupsButton;
private CheckBox portraisButton;
private FileSelectionBox fileSelectionBox;
public ModelExportDialog(Node source) {
this.source = source;
createGui();
}
private void createGui() {
source.setCursor(Cursor.WAIT);
gridPane.setHgap(10);
gridPane.setVgap(10);
gridPane.setPadding(new Insets(20, 150, 10, 10));
Label l = new Label("Export to file");
gridPane.add(l, 0, 0);
fileSelectionBox = new FileSelectionBox();
fileSelectionBox.useSaveDialog();
fileSelectionBox.disableValidation();
gridPane.add(fileSelectionBox, 1, 0);
GridPane.setValignment(l, VPos.TOP);
notesButton = new CheckBox("notes");
notesButton.setSelected(true);
groupsButton = new CheckBox("groups");
groupsButton.setSelected(true);
portraisButton = new CheckBox("portraits");
portraisButton.setSelected(true);
var row = new VBox();
row.getChildren().addAll(notesButton, groupsButton, portraisButton);
VBox.setMargin(notesButton, new Insets(5));
VBox.setMargin(groupsButton, new Insets(5));
VBox.setMargin(portraisButton, new Insets(5));
gridPane.add(row, 1, 1);
}
public ExportOptions showAndWait() {
try {
boolean confirmed = Dialogs.showCustomInput(source.getScene(), "Export model list", gridPane);
if (confirmed) {
Set<ExportIncludes> exportIncludes = new HashSet<>();
if (notesButton.isSelected()) {
exportIncludes.add(NOTES);
}
if (groupsButton.isSelected()) {
exportIncludes.add(GROUPS);
}
if (portraisButton.isSelected()) {
exportIncludes.add(PORTRAITS);
}
return new ExportOptions(exportIncludes, new File(fileSelectionBox.fileProperty().getValue()));
}
return null;
} finally {
source.setCursor(Cursor.DEFAULT);
}
}
}

View File

@ -0,0 +1,184 @@
package ctbrec.ui.tabs.recorded;
import com.squareup.moshi.*;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.io.FileJsonAdapter;
import ctbrec.io.LocalTimeJsonAdapter;
import ctbrec.io.ModelJsonAdapter;
import ctbrec.io.UuidJSonAdapter;
import ctbrec.sites.Site;
import okio.Buffer;
import okio.Okio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalTime;
import java.util.*;
public class ModelImportExport {
private static final Logger LOG = LoggerFactory.getLogger(ModelImportExport.class);
enum ExportIncludes {
NOTES,
GROUPS,
PORTRAITS
}
record ExportOptions(Set<ExportIncludes> includes, File targetFile) {
}
private ModelImportExport() {
}
public static void exportTo(List<Model> models, Config config, ExportOptions exportOptions) throws IOException {
Moshi moshi = new Moshi.Builder()
.add(Model.class, new ModelJsonAdapter())
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.add(LocalTime.class, new LocalTimeJsonAdapter())
.build();
JsonAdapter<Map<String, String>> notesAdapter = moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
JsonAdapter<List<Model>> modelListAdapter = moshi.adapter(Types.newParameterizedType(List.class, Model.class));
JsonAdapter<Set<ModelGroup>> modelGroupAdapter = moshi.adapter(Types.newParameterizedType(Set.class, ModelGroup.class));
try (JsonWriter writer = JsonWriter.of(Okio.buffer(Okio.sink(exportOptions.targetFile())))) {
writer.setIndent(" ");
writer.beginObject();
writer.name("models");
modelListAdapter.toJson(writer, models);
if (exportOptions.includes().contains(ExportIncludes.NOTES)) {
writer.name("notes");
notesAdapter.toJson(writer, config.getSettings().modelNotes);
}
if (exportOptions.includes().contains(ExportIncludes.GROUPS)) {
writer.name("groups");
modelGroupAdapter.toJson(writer, config.getSettings().modelGroups);
}
if (exportOptions.includes().contains(ExportIncludes.PORTRAITS)) {
var portraits = config.getSettings().modelPortraits;
var portraitLoader = new PortraitStore(config);
if (portraits != null && !portraits.isEmpty()) {
writer.name("portraits");
writer.beginArray();
for (Map.Entry<String, String> entry : config.getSettings().modelPortraits.entrySet()) {
String modelUrl = entry.getKey();
String portraitId = entry.getValue();
Optional<byte[]> portrait = portraitLoader.loadModelPortraitFile(modelUrl);
if (portrait.isPresent()) {
writer.beginObject();
writer.name("url").value(modelUrl);
writer.name("id").value(portraitId);
writer.name("data").value(Base64.getEncoder().encodeToString(portrait.get()));
writer.endObject();
}
}
writer.endArray();
}
}
writer.endObject();
}
}
public static List<Model> importFrom(File target, List<Site> sites, Config config) throws IOException {
Moshi moshi = new Moshi.Builder()
.add(Model.class, new ModelJsonAdapter(sites))
.add(File.class, new FileJsonAdapter())
.add(UUID.class, new UuidJSonAdapter())
.add(LocalTime.class, new LocalTimeJsonAdapter())
.build();
JsonAdapter<Model> modelAdapter = moshi.adapter(Model.class);
JsonAdapter<Map<String, String>> notesAdapter = moshi.adapter(Types.newParameterizedType(Map.class, String.class, String.class));
JsonAdapter<Set<ModelGroup>> modelGroupAdapter = moshi.adapter(Types.newParameterizedType(Set.class, ModelGroup.class));
List<Model> models = null;
String json = Files.readString(target.toPath(), StandardCharsets.UTF_8);
try (Buffer buffer = new Buffer()) {
JsonReader reader = JsonReader.of(buffer.writeUtf8(json));
reader.setLenient(true);
reader.beginObject();
while (reader.hasNext()) {
var next = reader.nextName();
switch (next) {
case "models" -> models = readModels(modelAdapter, reader);
case "notes" -> importNotes(reader, notesAdapter, config);
case "groups" -> importGroups(reader, modelGroupAdapter, config);
case "portraits" -> importPortraits(reader, config);
default -> LOG.warn("Element {} unknown", next);
}
}
reader.endObject();
}
return models;
}
private static void importPortraits(JsonReader reader, Config config) throws IOException {
PortraitStore portraitStore = new PortraitStore(config);
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject();
String url = null;
String id = null;
String dataBase64 = null;
while (reader.hasNext()) {
var name = reader.nextName();
switch (name) {
case "url" -> url = reader.nextString();
case "id" -> id = reader.nextString();
case "data" -> dataBase64 = reader.nextString();
default -> {
LOG.warn("Portrait element {} unknown", name);
reader.skipValue();
}
}
}
portraitStore.writePortrait(id, Base64.getDecoder().decode(dataBase64));
config.getSettings().modelPortraits.put(url, id);
reader.endObject();
}
reader.endArray();
}
private static void importGroups(JsonReader reader, JsonAdapter<Set<ModelGroup>> modelGroupAdapter, Config config) throws IOException {
var groups = modelGroupAdapter.fromJson(reader);
if (groups != null) {
config.getSettings().modelGroups.addAll(groups);
}
}
private static void importNotes(JsonReader reader, JsonAdapter<Map<String, String>> notesAdapter, Config config) throws IOException {
var notes = notesAdapter.fromJson(reader);
if (notes != null) {
config.getSettings().modelNotes.putAll(notes);
}
}
private static List<Model> readModels(JsonAdapter<Model> modelAdapter, JsonReader reader) throws IOException {
List<Model> result = new LinkedList<>();
reader.beginArray();
while (reader.hasNext()) {
try {
JsonReader.Token token = reader.peek();
if (token == JsonReader.Token.BEGIN_OBJECT) {
Model model = modelAdapter.fromJson(reader);
result.add(model);
} else if (token == JsonReader.Token.NAME) {
reader.skipName();
} else {
reader.skipValue();
}
} catch (Exception e) {
LOG.error("Couldn't parse model json", e);
}
}
reader.endArray();
return result;
}
}

View File

@ -0,0 +1,47 @@
package ctbrec.ui.tabs.recorded;
import ctbrec.Config;
import ctbrec.StringUtil;
import javafx.scene.image.Image;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Optional;
import static ctbrec.ui.action.AbstractPortraitAction.FORMAT;
public record PortraitStore(Config config) {
private static final Logger LOG = LoggerFactory.getLogger(PortraitStore.class);
public Optional<Image> loadModelPortrait(String modelUrl) {
return loadModelPortraitFile(modelUrl).map(bytes -> new Image(new ByteArrayInputStream(bytes)));
}
public Optional<byte[]> loadModelPortraitFile(String modelUrl) {
String portraitId = config.getSettings().modelPortraits.get(modelUrl);
if (StringUtil.isNotBlank(portraitId)) {
File configDir = config.getConfigDir();
File portraitDir = new File(configDir, "portraits");
File portraitFile = new File(portraitDir, portraitId + '.' + FORMAT);
try {
return Optional.of(Files.readAllBytes(portraitFile.toPath()));
} catch (IOException e) {
LOG.error("Couldn't load portrait file {}", portraitFile, e);
}
}
return Optional.empty();
}
public void writePortrait(String id, byte[] data) throws IOException {
File configDir = config.getConfigDir();
File portraitDir = new File(configDir, "portraits");
File portraitFile = new File(portraitDir, id + '.' + FORMAT);
Files.createDirectories(portraitDir.toPath());
Files.write(portraitFile.toPath(), data);
}
}

View File

@ -1,18 +1,5 @@
package ctbrec.ui.tabs.recorded;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.recorder.Recorder;
@ -30,6 +17,18 @@ import javafx.concurrent.WorkerStateEvent;
import javafx.geometry.Insets;
import javafx.scene.layout.BorderPane;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class RecordLaterTab extends AbstractRecordedModelsTab implements TabSelectionListener {
private static final Logger LOG = LoggerFactory.getLogger(RecordLaterTab.class);
@ -69,6 +68,11 @@ public class RecordLaterTab extends AbstractRecordedModelsTab implements TabSele
restoreState();
}
@Override
protected List<Model> getExportList() {
return recorder.getModels().stream().filter(Model::isMarkedForLaterRecording).toList();
}
void initializeUpdateService() {
updateService = createUpdateService();
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));

View File

@ -38,11 +38,7 @@ public class RecordedModelsPerSiteTab extends RecordedModelsTab implements TabSe
protected void pauseAll(ActionEvent evt) {
boolean yes = Dialogs.showConfirmDialog("Pause all models", "", "Pause the recording of all models in this table?", getTabPane().getScene());
if (yes) {
List<Model> models = recorder.getModels().stream()
.filter(Predicate.not(Model::isMarkedForLaterRecording))
.filter(m -> Objects.equals(m.getSite(), sites.get(0)))
.collect(Collectors.toList());
new PauseAction(getTabPane(), models, recorder).execute();
new PauseAction(getTabPane(), getFilteredModelsForTab(), recorder).execute();
}
}
@ -50,11 +46,19 @@ public class RecordedModelsPerSiteTab extends RecordedModelsTab implements TabSe
protected void resumeAll(ActionEvent evt) {
boolean yes = Dialogs.showConfirmDialog("Resume all models", "", "Pause the recording of all models in this table?", getTabPane().getScene());
if (yes) {
List<Model> models = recorder.getModels().stream()
.filter(Predicate.not(Model::isMarkedForLaterRecording))
.filter(m -> Objects.equals(m.getSite(), sites.get(0)))
.collect(Collectors.toList());
new ResumeAction(getTabPane(), models, recorder).execute();
new ResumeAction(getTabPane(), getFilteredModelsForTab(), recorder).execute();
}
}
@Override
protected List<Model> getExportList() {
return getFilteredModelsForTab();
}
private List<Model> getFilteredModelsForTab() {
return recorder.getModels().stream()
.filter(Predicate.not(Model::isMarkedForLaterRecording))
.filter(m -> Objects.equals(m.getSite(), sites.get(0)))
.toList();
}
}

View File

@ -169,6 +169,11 @@ public class RecordedModelsTab extends AbstractRecordedModelsTab implements TabS
restoreState();
}
@Override
protected List<Model> getExportList() {
return recorder.getModels().stream().filter(Predicate.not(Model::isMarkedForLaterRecording)).toList();
}
private void onUpdatePriority(CellEditEvent<JavaFxModel, Number> evt) {
try {
int prio = Optional.ofNullable(evt.getNewValue()).map(Number::intValue).orElse(-1);

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

View File

@ -265,12 +265,14 @@ public class NextGenLocalRecorder implements Recorder {
@Override
public void addModel(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
Optional<Model> result = findModel(model);
if (!result.isPresent()) {
if (result.isEmpty()) {
LOG.info("Model {} added", model);
recorderLock.lock();
try {
models.add(model);
model.setAddedTimestamp(Instant.now());
if (Objects.equals(model.getAddedTimestamp(), Instant.EPOCH)) {
model.setAddedTimestamp(Instant.now());
}
config.getSettings().models.add(model);
config.save();
} catch (IOException e) {