Added actual AES 256 encryption, and file operations to UI.
This commit is contained in:
parent
5ab1abbfff
commit
c09923906c
|
@ -2,6 +2,7 @@ module crystalkeep {
|
|||
requires javafx.fxml;
|
||||
requires javafx.controls;
|
||||
|
||||
opens nl.andrewlalis.crystalkeep.control;
|
||||
opens nl.andrewlalis.crystalkeep;
|
||||
|
||||
exports nl.andrewlalis.crystalkeep.control to javafx.fxml;
|
||||
|
|
|
@ -3,6 +3,7 @@ package nl.andrewlalis.crystalkeep;
|
|||
import javafx.application.Application;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.stage.Stage;
|
||||
import nl.andrewlalis.crystalkeep.control.MainViewController;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
|
@ -13,10 +14,12 @@ import nl.andrewlalis.crystalkeep.model.shards.TextShard;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CrystalKeep extends Application {
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) throws GeneralSecurityException {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
|
@ -29,7 +32,7 @@ public class CrystalKeep extends Application {
|
|||
stage.setScene(scene);
|
||||
stage.setTitle("CrystalKeep");
|
||||
stage.sizeToScene();
|
||||
model.setActiveCluster(this.loadRootCluster());
|
||||
model.setActiveCluster(null);
|
||||
|
||||
stage.show();
|
||||
|
||||
|
@ -37,22 +40,33 @@ public class CrystalKeep extends Application {
|
|||
controller.init(model);
|
||||
}
|
||||
|
||||
private Cluster loadRootCluster() throws IOException {
|
||||
ClusterLoader clusterLoader = new ClusterLoader();
|
||||
Cluster rootCluster;
|
||||
try {
|
||||
rootCluster = clusterLoader.loadDefault();
|
||||
System.out.println("Loaded existing root cluster.");
|
||||
} catch (IOException e) {
|
||||
rootCluster = new Cluster("Root");
|
||||
rootCluster.addShard(new TextShard("Example Shard", LocalDateTime.now(), "Hello world!"));
|
||||
rootCluster.addShard(new LoginCredentialsShard("Netflix", LocalDateTime.now(), "user", "secret password"));
|
||||
for (int i = 0; i < 100; i++) {
|
||||
rootCluster.addShard(new TextShard("test " + i, LocalDateTime.now(), "value: " + i));
|
||||
}
|
||||
clusterLoader.saveDefault(rootCluster);
|
||||
System.out.println("Saved root cluster on first load.");
|
||||
}
|
||||
return rootCluster;
|
||||
}
|
||||
// private Cluster loadRootCluster() throws IOException, GeneralSecurityException {
|
||||
// ClusterLoader clusterLoader = new ClusterLoader();
|
||||
// Cluster rootCluster;
|
||||
//
|
||||
//// Optional<Cluster> oc = clusterLoader.load(ClusterLoader.DEFAULT_CLUSTER);
|
||||
//// if (oc.isEmpty()) {
|
||||
//// new Alert(Alert.AlertType.ERROR, "Could not load cluster.").show();
|
||||
//// System.exit(1);
|
||||
//// }
|
||||
//// return oc.get();
|
||||
//
|
||||
//// try {
|
||||
//// long start = System.currentTimeMillis();
|
||||
//// rootCluster = clusterLoader.load(ClusterLoader.DEFAULT_CLUSTER);
|
||||
//// long dur = System.currentTimeMillis() - start;
|
||||
//// System.out.println("Loaded existing root cluster in " + dur + " ms.");
|
||||
//// } catch (Exception e) {
|
||||
//// e.printStackTrace();
|
||||
//// rootCluster = new Cluster("Root");
|
||||
//// rootCluster.addShard(new TextShard("Example Shard", LocalDateTime.now(), "Hello world!"));
|
||||
//// rootCluster.addShard(new LoginCredentialsShard("Netflix", LocalDateTime.now(), "user", "secret password"));
|
||||
//// for (int i = 0; i < 100; i++) {
|
||||
//// rootCluster.addShard(new TextShard("test " + i, LocalDateTime.now(), "value: " + i));
|
||||
//// }
|
||||
//// clusterLoader.save(rootCluster, ClusterLoader.DEFAULT_CLUSTER, "test");
|
||||
//// System.out.println("Saved root cluster on first load.");
|
||||
//// }
|
||||
//// return rootCluster;
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.Model;
|
||||
|
||||
public class AddClusterHandler implements EventHandler<ActionEvent> {
|
||||
private final Cluster cluster;
|
||||
private final Model model;
|
||||
|
||||
public AddClusterHandler(Cluster cluster, Model model) {
|
||||
this.cluster = cluster;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
Dialog<String> d = new TextInputDialog();
|
||||
d.setContentText("Enter the name of the new cluster.");
|
||||
d.setHeaderText(null);
|
||||
d.setTitle("Add Cluster");
|
||||
d.setGraphic(null);
|
||||
d.showAndWait().ifPresent(s -> {
|
||||
cluster.addCluster(new Cluster(s.trim()));
|
||||
model.notifyListeners();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.Model;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
import nl.andrewlalis.crystalkeep.model.ShardType;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AddShardHandler implements EventHandler<ActionEvent> {
|
||||
private final Cluster cluster;
|
||||
private final Model model;
|
||||
|
||||
public AddShardHandler(Cluster cluster, Model model) {
|
||||
this.cluster = cluster;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
Dialog<String> d = new TextInputDialog();
|
||||
d.setContentText("Enter the name of the new shard.");
|
||||
d.showAndWait().ifPresent(s -> {
|
||||
List<String> choices = Arrays.stream(ShardType.values())
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.toList());
|
||||
Dialog<String> d1 = new ChoiceDialog<>("TEXT", choices);
|
||||
d1.setContentText("Choose the type of shard to create.");
|
||||
d1.showAndWait().ifPresent(typeName -> {
|
||||
ShardType type = ShardType.valueOf(typeName.toUpperCase());
|
||||
Shard shard;
|
||||
switch (type) {
|
||||
case TEXT:
|
||||
shard = new TextShard(s);
|
||||
break;
|
||||
case LOGIN_CREDENTIALS:
|
||||
shard = new LoginCredentialsShard(s);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid shard type selected.");
|
||||
}
|
||||
cluster.addShard(shard);
|
||||
model.notifyListeners();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.VBox;
|
||||
import nl.andrewlalis.crystalkeep.model.CrystalItem;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
|
||||
import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.LoginCredentialsPane;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.ShardPane;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.TextShardPane;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClusterTreeViewItemSelectionListener implements ChangeListener<TreeItem<CrystalItem>> {
|
||||
private static final Map<Class<? extends Shard>, Class<? extends ShardPane<? extends Shard>>> shardPanesMap = new HashMap<>();
|
||||
static {
|
||||
shardPanesMap.put(TextShard.class, TextShardPane.class);
|
||||
shardPanesMap.put(LoginCredentialsShard.class, LoginCredentialsPane.class);
|
||||
}
|
||||
|
||||
private final VBox shardDetailContainer;
|
||||
|
||||
public ClusterTreeViewItemSelectionListener(VBox shardDetailContainer) {
|
||||
this.shardDetailContainer = shardDetailContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends TreeItem<CrystalItem>> observable, TreeItem<CrystalItem> oldValue, TreeItem<CrystalItem> newValue) {
|
||||
shardDetailContainer.getChildren().clear();
|
||||
if (newValue instanceof ShardTreeItem) {
|
||||
var node = (ShardTreeItem) newValue;
|
||||
var paneClass = shardPanesMap.get(node.getShard().getClass());
|
||||
try {
|
||||
var pane = paneClass.getDeclaredConstructor(node.getShard().getClass()).newInstance(node.getShard());
|
||||
shardDetailContainer.getChildren().add(pane);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.CrystalItem;
|
||||
import nl.andrewlalis.crystalkeep.model.Model;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
import nl.andrewlalis.crystalkeep.view.ClusterTreeItem;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class DeleteItemHandler implements EventHandler<ActionEvent> {
|
||||
private final TreeItem<CrystalItem> treeItem;
|
||||
private final Model model;
|
||||
|
||||
public DeleteItemHandler(TreeItem<CrystalItem> treeItem, Model model) {
|
||||
this.treeItem = treeItem;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
if (this.treeItem.getParent() != null && this.treeItem.getParent() instanceof ClusterTreeItem) {
|
||||
Optional<ButtonType> result = new Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to delete this item?").showAndWait();
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
var cluster = ((ClusterTreeItem) this.treeItem.getParent()).getCluster();
|
||||
var item = this.treeItem.getValue();
|
||||
if (item instanceof Shard) {
|
||||
cluster.removeShard((Shard) item);
|
||||
} else if (item instanceof Cluster) {
|
||||
cluster.removeCluster((Cluster) item);
|
||||
}
|
||||
this.model.notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +1,24 @@
|
|||
package nl.andrewlalis.crystalkeep.control;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import nl.andrewlalis.crystalkeep.model.*;
|
||||
import nl.andrewlalis.crystalkeep.model.serialization.ClusterLoader;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
|
||||
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
|
||||
import nl.andrewlalis.crystalkeep.view.ClusterTreeItem;
|
||||
import nl.andrewlalis.crystalkeep.view.CrystalItemTreeCell;
|
||||
import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.LoginCredentialsPane;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.ShardPane;
|
||||
import nl.andrewlalis.crystalkeep.view.shard_details.TextShardPane;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
public class MainViewController implements ModelListener {
|
||||
private static final Map<Class<? extends Shard>, Class<? extends ShardPane<? extends Shard>>> shardPanesMap = new HashMap<>();
|
||||
static {
|
||||
shardPanesMap.put(TextShard.class, TextShardPane.class);
|
||||
shardPanesMap.put(LoginCredentialsShard.class, LoginCredentialsPane.class);
|
||||
}
|
||||
|
||||
private Model model;
|
||||
|
||||
@FXML
|
||||
|
@ -40,29 +32,8 @@ public class MainViewController implements ModelListener {
|
|||
this.activeClusterUpdated();
|
||||
assert(this.clusterTreeView != null);
|
||||
this.clusterTreeView.setCellFactory(param -> new CrystalItemTreeCell(this.model));
|
||||
this.clusterTreeView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
shardDetailContainer.getChildren().clear();
|
||||
if (newValue instanceof ShardTreeItem) {
|
||||
var node = (ShardTreeItem) newValue;
|
||||
var paneClass = shardPanesMap.get(node.getShard().getClass());
|
||||
try {
|
||||
var pane = paneClass.getDeclaredConstructor(node.getShard().getClass()).newInstance(node.getShard());
|
||||
shardDetailContainer.getChildren().add(pane);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void exit(ActionEvent event) {
|
||||
System.out.println("Exiting...");
|
||||
try {
|
||||
new ClusterLoader().saveDefault(model.getActiveCluster());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.clusterTreeView.getSelectionModel().selectedItemProperty()
|
||||
.addListener(new ClusterTreeViewItemSelectionListener(this.shardDetailContainer));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,4 +55,62 @@ public class MainViewController implements ModelListener {
|
|||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void exit() {
|
||||
Platform.exit();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void load() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Load a Cluster");
|
||||
chooser.setInitialDirectory(ClusterLoader.CLUSTER_PATH.toFile());
|
||||
chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Cluster Files", "cts"));
|
||||
File file = chooser.showOpenDialog(this.clusterTreeView.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
ClusterLoader loader = new ClusterLoader();
|
||||
var password = loader.promptPassword();
|
||||
if (password.isEmpty() || password.get().isEmpty()) return;
|
||||
try {
|
||||
var cluster = loader.load(file.toPath(), password.get());
|
||||
model.setActiveCluster(cluster);
|
||||
model.setActiveClusterPath(file.toPath());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
new Alert(Alert.AlertType.WARNING, "Could not load cluster.").showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void save() {
|
||||
if (model.getActiveCluster() == null) return;
|
||||
ClusterLoader loader = new ClusterLoader();
|
||||
Path path = model.getActiveClusterPath();
|
||||
if (path == null) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Save Cluster");
|
||||
chooser.setInitialDirectory(ClusterLoader.CLUSTER_PATH.toFile());
|
||||
chooser.setSelectedExtensionFilter(new FileChooser.ExtensionFilter("Cluster Files", "cts"));
|
||||
File file = chooser.showSaveDialog(this.clusterTreeView.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
path = file.toPath();
|
||||
}
|
||||
var password = loader.promptPassword();
|
||||
if (password.isEmpty() || password.get().isEmpty()) return;
|
||||
try {
|
||||
new ClusterLoader().save(model.getActiveCluster(), path, password.get());
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
e.printStackTrace();
|
||||
var alert = new Alert(Alert.AlertType.ERROR, "Could not save cluster.");
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void newCluster() {
|
||||
Cluster c = new Cluster("Root");
|
||||
model.setActiveCluster(c);
|
||||
model.setActiveClusterPath(null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package nl.andrewlalis.crystalkeep.model;
|
||||
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class Model {
|
||||
private Cluster activeCluster;
|
||||
private Path activeClusterPath;
|
||||
|
||||
private final Set<ModelListener> listeners = new HashSet<>();
|
||||
|
||||
|
@ -13,6 +15,10 @@ public class Model {
|
|||
return activeCluster;
|
||||
}
|
||||
|
||||
public Path getActiveClusterPath() {
|
||||
return activeClusterPath;
|
||||
}
|
||||
|
||||
public void addListener(ModelListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
@ -22,6 +28,10 @@ public class Model {
|
|||
this.notifyListeners();
|
||||
}
|
||||
|
||||
public void setActiveClusterPath(Path activeClusterPath) {
|
||||
this.activeClusterPath = activeClusterPath;
|
||||
}
|
||||
|
||||
public void notifyListeners() {
|
||||
this.listeners.forEach(ModelListener::activeClusterUpdated);
|
||||
}
|
||||
|
|
|
@ -1,27 +1,62 @@
|
|||
package nl.andrewlalis.crystalkeep.model.serialization;
|
||||
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ClusterLoader {
|
||||
private static final Path CLUSTER_PATH = Path.of("clusters");
|
||||
private static final Path DEFAULT_CLUSTER = CLUSTER_PATH.resolve("default.cts");
|
||||
public static final Path CLUSTER_PATH = Path.of("clusters");
|
||||
public static final Path DEFAULT_CLUSTER = CLUSTER_PATH.resolve("default.cts");
|
||||
private static final byte[] SALT = "zf9i78vy".getBytes(StandardCharsets.UTF_8);
|
||||
private static final byte[] IV = "Fafioje;a324fsde".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
public Cluster loadDefault() throws IOException {
|
||||
InputStream is = new FileInputStream(DEFAULT_CLUSTER.toFile());
|
||||
return ClusterSerializer.readCluster(is);
|
||||
public Optional<String> promptPassword() {
|
||||
Dialog<String> d = new TextInputDialog();
|
||||
d.setContentText("Enter the password");
|
||||
d.setGraphic(null);
|
||||
d.setHeaderText(null);
|
||||
d.setTitle("Enter Password");
|
||||
return d.showAndWait();
|
||||
}
|
||||
|
||||
public void saveDefault(Cluster cluster) throws IOException {
|
||||
public Cluster load(Path path, String password) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, this.getSecretKey(password), new IvParameterSpec(IV));
|
||||
byte[] raw = Files.readAllBytes(path);
|
||||
return ClusterSerializer.readCluster(new ByteArrayInputStream(cipher.doFinal(raw)));
|
||||
}
|
||||
|
||||
public void save(Cluster cluster, Path path, String password) throws IOException, GeneralSecurityException {
|
||||
Files.createDirectories(CLUSTER_PATH);
|
||||
OutputStream os = Files.newOutputStream(DEFAULT_CLUSTER);
|
||||
ClusterSerializer.writeCluster(cluster, os);
|
||||
os.close();
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
ClusterSerializer.writeCluster(cluster, bos);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, this.getSecretKey(password), new IvParameterSpec(IV));
|
||||
Files.write(path, cipher.doFinal(bos.toByteArray()));
|
||||
bos.close();
|
||||
}
|
||||
|
||||
private SecretKey getSecretKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), SALT, 65536, 256);
|
||||
return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,10 @@ public class LoginCredentialsShard extends Shard {
|
|||
this.password = password;
|
||||
}
|
||||
|
||||
public LoginCredentialsShard(String name) {
|
||||
this(name, LocalDateTime.now(), "", "");
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ public class TextShard extends Shard {
|
|||
this.text = text;
|
||||
}
|
||||
|
||||
public TextShard(String name) {
|
||||
this(name, LocalDateTime.now(), "");
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ import javafx.scene.control.MenuItem;
|
|||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import nl.andrewlalis.crystalkeep.control.AddClusterHandler;
|
||||
import nl.andrewlalis.crystalkeep.control.AddShardHandler;
|
||||
import nl.andrewlalis.crystalkeep.control.DeleteItemHandler;
|
||||
import nl.andrewlalis.crystalkeep.model.Cluster;
|
||||
import nl.andrewlalis.crystalkeep.model.CrystalItem;
|
||||
import nl.andrewlalis.crystalkeep.model.Model;
|
||||
import nl.andrewlalis.crystalkeep.model.Shard;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
@ -27,28 +29,23 @@ public class CrystalItemTreeCell extends TreeCell<CrystalItem> {
|
|||
if (!empty) {
|
||||
ContextMenu menu = new ContextMenu();
|
||||
if (item instanceof Cluster) {
|
||||
var cluster = (Cluster) item;
|
||||
var addShardItem = new MenuItem("Add Shard");
|
||||
addShardItem.setOnAction(new AddShardHandler(cluster, model));
|
||||
var addClusterItem = new MenuItem("Add Cluster");
|
||||
addClusterItem.setOnAction(new AddClusterHandler(cluster, model));
|
||||
menu.getItems().addAll(addShardItem, addClusterItem);
|
||||
}
|
||||
var deleteItem = new MenuItem("Delete");
|
||||
deleteItem.setOnAction(event -> {
|
||||
if (this.getTreeItem().getParent() != null && this.getTreeItem().getParent() instanceof ClusterTreeItem) {
|
||||
var cluster = ((ClusterTreeItem) this.getTreeItem().getParent()).getCluster();
|
||||
if (item instanceof Shard) {
|
||||
cluster.removeShard((Shard) item);
|
||||
} else if (item instanceof Cluster) {
|
||||
cluster.removeCluster((Cluster) item);
|
||||
}
|
||||
this.model.notifyListeners();
|
||||
}
|
||||
});
|
||||
menu.getItems().add(deleteItem);
|
||||
if (this.getTreeItem().getParent() != null && this.getTreeItem().getParent() instanceof ClusterTreeItem) {
|
||||
var deleteItem = new MenuItem("Delete");
|
||||
deleteItem.setOnAction(new DeleteItemHandler(this.getTreeItem(), this.model));
|
||||
menu.getItems().add(deleteItem);
|
||||
}
|
||||
this.setText(item.getName());
|
||||
InputStream is = getClass().getResourceAsStream(item.getIconPath());
|
||||
if (is != null) {
|
||||
ImageView icon = new ImageView(new Image(is));
|
||||
icon.setFitHeight(24);
|
||||
icon.setFitHeight(16);
|
||||
icon.setPreserveRatio(true);
|
||||
this.setGraphic(icon);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ public class TextShardPane extends ShardPane<TextShard> {
|
|||
@Override
|
||||
protected Node getContent(TextShard shard) {
|
||||
var textArea = new TextArea(shard.getText());
|
||||
textArea.setWrapText(true);
|
||||
textArea.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
shard.setText(newValue);
|
||||
});
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewlalis.crystalkeep.control.MainViewController">
|
||||
<MenuBar>
|
||||
<Menu text="File">
|
||||
<MenuItem text="New" onAction="#newCluster" />
|
||||
<MenuItem text="Load" onAction="#load" />
|
||||
<MenuItem text="Save" onAction="#save" />
|
||||
<MenuItem text="Exit" onAction="#exit" />
|
||||
</Menu>
|
||||
</MenuBar>
|
||||
|
|
Loading…
Reference in New Issue