Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (C) 2019 European Spallation Source ERIC.
* Copyright (C) 2024 European Spallation Source ERIC.
* <p>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -118,6 +118,8 @@ public class Messages {
public static String nodeSelectionForConfiguration;
public static String noValueAvailable;
public static String readbackPVName;
public static String restoreFailed;
public static String restoreFailedPVs;
public static String saveFilter;

public static String saveFilterConfirmOverwrite;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,18 @@ public interface SaveAndRestoreClient {
* an exception.
*/
UserData authenticate(String userName, String password);

/**
* Requests service to restore the specified {@link SnapshotItem}s
* @param snapshotItems A {@link List} of {@link SnapshotItem}s
* @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s.
*/
List<RestoreResult> restore(List<SnapshotItem> snapshotItems);

/**
* Requests service to restore the specified snapshot.
* @param snapshotNodeId Unique id of a snapshot
* @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s.
*/
List<RestoreResult> restore(String snapshotNodeId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -638,4 +638,55 @@ public UserData authenticate(String userName, String password) {
return response.getEntity(new GenericType<>() {
});
}

@Override
public List<RestoreResult> restore(List<SnapshotItem> snapshotItems){
WebResource webResource =
getClient().resource(Preferences.jmasarServiceUrl + "/restore/items");
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
.entity(snapshotItems, CONTENT_TYPE_JSON)
.post(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
}
if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = "Restore failed";
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
return response.getEntity(new GenericType<>() {
});
}


public List<RestoreResult> restore(String snapshotNodeId){
WebResource webResource =
getClient()
.resource(Preferences.jmasarServiceUrl + "/restore/node")
.queryParam("codeId", snapshotNodeId);
ClientResponse response;
try {
response = webResource.accept(CONTENT_TYPE_JSON)
.post(ClientResponse.class);
} catch (UniformInterfaceException e) {
throw new RuntimeException(e);
}
if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
String message = "Restore failed";
try {
message = new String(response.getEntityInputStream().readAllBytes());
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to parse response", e);
}
throw new SaveAndRestoreClientException(message);
}
return response.getEntity(new GenericType<>() {
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,4 +394,26 @@ public UserData authenticate(String userName, String password) throws Exception{
executor.submit(() -> saveAndRestoreClient.authenticate(userName, password));
return future.get();
}

/**
* Requests service to restore the specified {@link SnapshotItem}s
* @param snapshotItems A {@link List} of {@link SnapshotItem}s
* @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s.
*/
public List<RestoreResult> restore(List<SnapshotItem> snapshotItems) throws Exception{
Future<List<RestoreResult>> future =
executor.submit(() -> saveAndRestoreClient.restore(snapshotItems));
return future.get();
}

/**
* Requests service to restore the specified snapshot.
* @param snapshotNodeId Unique id of a snapshot
* @return A @{@link List} of {@link RestoreResult}s with information on potentially failed {@link SnapshotItem}s.
*/
public List<RestoreResult> restore(String snapshotNodeId) throws Exception{
Future<List<RestoreResult>> future =
executor.submit(() -> saveAndRestoreClient.restore(snapshotNodeId));
return future.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,21 @@
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.text.Font;
import javafx.scene.text.Text;

import org.epics.vtype.VType;
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.SaveAndRestoreApplication;
Expand All @@ -42,18 +49,16 @@
import org.phoebus.ui.javafx.FocusUtil;
import org.phoebus.util.time.TimestampFormats;

import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static org.phoebus.ui.application.PhoebusApplication.logger;

/**
* Base controller class for the snapshot table view. It handles common items (UI components, methods) needed
* by all subclasses.
Expand Down Expand Up @@ -93,8 +98,6 @@ public abstract class BaseSnapshotTableViewController {

protected SnapshotController snapshotController;

protected static boolean resizePolicyNotInitialized = true;

protected static final Logger LOGGER = Logger.getLogger(BaseSnapshotTableViewController.class.getName());

@FXML
Expand Down Expand Up @@ -127,27 +130,6 @@ public abstract class BaseSnapshotTableViewController {
*/
protected List<Snapshot> snapshots = new ArrayList<>();

public BaseSnapshotTableViewController() {
if (resizePolicyNotInitialized) {
AccessController.doPrivileged(resizePolicyAction);
}
}

protected static PrivilegedAction<Object> resizePolicyAction = () -> {
try {
// Java FX bugfix: the table columns are not properly resized for the first table
Field f = TableView.CONSTRAINED_RESIZE_POLICY.getClass().getDeclaredField("isFirstRun");
f.setAccessible(true);
f.set(TableView.CONSTRAINED_RESIZE_POLICY, Boolean.FALSE);
} catch (NoSuchFieldException | IllegalAccessException | RuntimeException e) {
// ignore
}
// Even if failed to set the policy, pretend that it was set. In such case the UI will be slightly dorked the
// first time, but will be OK in all other cases.
resizePolicyNotInitialized = false;
return null;
};

public void initialize() {
snapshotTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
snapshotTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
Expand Down Expand Up @@ -235,7 +217,7 @@ protected void updateItem(TableEntry item, boolean empty) {
VType updatedValue = e.getRowValue().readOnlyProperty().get() ? e.getOldValue() : e.getNewValue();
ObjectProperty<VTypePair> value = e.getRowValue().valueProperty();
value.setValue(new VTypePair(value.get().base, updatedValue, value.get().threshold));
snapshotController.updateLoadedSnapshot( e.getRowValue(), updatedValue);
snapshotController.updateLoadedSnapshot(e.getRowValue(), updatedValue);
});

liveValueColumn.setCellFactory(e -> new VTypeCellEditor<>());
Expand Down Expand Up @@ -278,11 +260,10 @@ protected String getPVKey(String pvName, boolean isReadonly) {
return pvName + "_" + isReadonly;
}

protected void showSnapshotInTable(Snapshot snapshot){
if(snapshots.isEmpty()){
protected void showSnapshotInTable(Snapshot snapshot) {
if (snapshots.isEmpty()) {
snapshots.add(snapshot);
}
else{
} else {
snapshots.set(0, snapshot);
}
AtomicInteger counter = new AtomicInteger(0);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (C) 2019 European Spallation Source ERIC.
* Copyright (C) 2024 European Spallation Source ERIC.
* <p>
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
Expand Down Expand Up @@ -42,6 +42,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
* This controller is for the use case of loading a configuration {@link Node} to take a new snapshot.
Expand Down Expand Up @@ -350,12 +351,28 @@ public void loadSnapshot(Node snapshotNode) {
}

public void restore(ActionEvent actionEvent) {
snapshotTableViewController.restore(snapshotProperty.get(), restoreFailedPVNames -> {
snapshotTableViewController.restore(snapshotProperty.get(), restoreResultList -> {
javafx.scene.Node jfxNode = (javafx.scene.Node) actionEvent.getSource();
String userData = (String) jfxNode.getUserData();
if (userData.equalsIgnoreCase("true")) {
eventReceivers.forEach(r -> r.snapshotRestored(snapshotProperty.get().getSnapshotNode(), restoreFailedPVNames, this::showLoggingError));
eventReceivers.forEach(r -> r.snapshotRestored(snapshotProperty.get().getSnapshotNode(), restoreResultList, this::showLoggingError));
}
if(restoreResultList != null && !restoreResultList.isEmpty()){
showFailedRestoreResult(restoreResultList);
}
});
}

private void showFailedRestoreResult(List<RestoreResult> restoreResultList){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(Messages.restoreFailedPVs).append(System.lineSeparator());
stringBuilder.append(restoreResultList.stream()
.map(r -> r.getSnapshotItem().getConfigPv().getPvName()).collect(Collectors.joining(System.lineSeparator())));
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(Messages.restoreFailed);
alert.setContentText(stringBuilder.toString());
alert.show();
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 European Spallation Source ERIC.
* Copyright (C) 2024 European Spallation Source ERIC.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
Expand All @@ -23,7 +23,13 @@
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import org.epics.vtype.VNumber;
Expand All @@ -32,16 +38,31 @@
import org.phoebus.applications.saveandrestore.Messages;
import org.phoebus.applications.saveandrestore.Preferences;
import org.phoebus.applications.saveandrestore.SafeMultiply;
import org.phoebus.applications.saveandrestore.model.*;
import org.phoebus.applications.saveandrestore.ui.*;
import org.phoebus.core.vtypes.VTypeHelper;
import org.phoebus.applications.saveandrestore.model.Node;
import org.phoebus.applications.saveandrestore.model.NodeType;
import org.phoebus.applications.saveandrestore.model.RestoreResult;
import org.phoebus.applications.saveandrestore.model.Snapshot;
import org.phoebus.applications.saveandrestore.model.SnapshotData;
import org.phoebus.applications.saveandrestore.model.SnapshotItem;
import org.phoebus.applications.saveandrestore.ui.SaveAndRestoreService;
import org.phoebus.applications.saveandrestore.ui.Threshold;
import org.phoebus.applications.saveandrestore.ui.Utilities;
import org.phoebus.applications.saveandrestore.ui.VNoData;
import org.phoebus.applications.saveandrestore.ui.VTypePair;
import org.phoebus.core.vtypes.VDisconnectedData;
import org.phoebus.core.vtypes.VTypeHelper;
import org.phoebus.framework.jobs.JobManager;
import org.phoebus.ui.dialog.DialogHelper;
import org.phoebus.util.time.TimestampFormats;

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
Expand Down Expand Up @@ -343,7 +364,54 @@ public void hideEqualItems() {
Platform.runLater(() -> updateTable(arrayList));
}

public void restore(Snapshot snapshot, Consumer<List<String>> completion) {
/**
* Restores a snapshot through a call to the remote service.
*
* @param snapshot The {@link Snapshot} object subject to restore
* @param completion A handler for the outcome of the restore operation
*/
public void restore(Snapshot snapshot, Consumer<List<RestoreResult>> completion) {
JobManager.schedule("Restore snapshot " + snapshot.getSnapshotNode().getName(), monitor -> {
List<SnapshotItem> itemsToRestore = new ArrayList<>();
List<RestoreResult> restoreResultList = null;
// Before invoking restore, compile a list of items where non-restorable PVs are excluded.
for (SnapshotItem entry : snapshot.getSnapshotData().getSnapshotItems()) {
TableEntry e = tableEntryItems.get(getPVKey(entry.getConfigPv().getPvName(), entry.getConfigPv().isReadOnly()));

boolean restorable = e.selectedProperty().get() &&
!e.readOnlyProperty().get() &&
entry.getValue() != null &&
!entry.getValue().equals(VNoData.INSTANCE);

if (restorable) {
itemsToRestore.add(entry);
}
}

try {
restoreResultList = SaveAndRestoreService.getInstance().restore(itemsToRestore);
} catch (Exception e) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle(Messages.errorActionFailed);
alert.setContentText(e.getMessage());
alert.setHeaderText(Messages.restoreFailed);
DialogHelper.positionDialog(alert, snapshotTableView, -150, -150);
alert.showAndWait();
});
}
completion.accept(restoreResultList);
});
}

/**
* Restores a snapshot from client.
*
* @param snapshot The {@link Snapshot} object subject to restore
* @param completion A handler for the outcome of the restore operation
*/
@SuppressWarnings("unused")
public void restoreFromClient(Snapshot snapshot, Consumer<List<String>> completion) {
new Thread(() -> {
List<String> restoreFailedPVNames = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(snapshot.getSnapshotData().getSnapshotItems().size());
Expand Down Expand Up @@ -394,6 +462,7 @@ public void restore(Snapshot snapshot, Consumer<List<String>> completion) {
}).start();
}


public void setShowDeltaPercentage(boolean showDeltaPercentage) {
this.showDeltaPercentage.set(showDeltaPercentage);
}
Expand Down
Loading