mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add delete wallet functionality, overwriting wallet file data first
This commit is contained in:
parent
7fb230e56b
commit
b5fa8f0ee0
7 changed files with 123 additions and 3 deletions
|
@ -111,6 +111,12 @@ public class AppController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem exportWallet;
|
private MenuItem exportWallet;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem deleteWallet;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem closeTab;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Menu fileMenu;
|
private Menu fileMenu;
|
||||||
|
|
||||||
|
@ -294,11 +300,13 @@ public class AppController implements Initializable {
|
||||||
EventManager.get().post(new TransactionTabsClosedEvent(closedTransactionTabs));
|
EventManager.get().post(new TransactionTabsClosedEvent(closedTransactionTabs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeTab.setDisable(tabs.getTabs().isEmpty());
|
||||||
if(tabs.getTabs().isEmpty()) {
|
if(tabs.getTabs().isEmpty()) {
|
||||||
Stage tabStage = (Stage)tabs.getScene().getWindow();
|
Stage tabStage = (Stage)tabs.getScene().getWindow();
|
||||||
tabStage.setTitle("Sparrow");
|
tabStage.setTitle("Sparrow");
|
||||||
saveTransaction.setVisible(true);
|
saveTransaction.setVisible(true);
|
||||||
saveTransaction.setDisable(true);
|
saveTransaction.setDisable(true);
|
||||||
|
exportWallet.setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -349,6 +357,8 @@ public class AppController implements Initializable {
|
||||||
savePSBTBinary.disableProperty().bind(saveTransaction.visibleProperty());
|
savePSBTBinary.disableProperty().bind(saveTransaction.visibleProperty());
|
||||||
showPSBT.visibleProperty().bind(saveTransaction.visibleProperty().not());
|
showPSBT.visibleProperty().bind(saveTransaction.visibleProperty().not());
|
||||||
exportWallet.setDisable(true);
|
exportWallet.setDisable(true);
|
||||||
|
deleteWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||||
|
closeTab.setDisable(true);
|
||||||
lockWallet.setDisable(true);
|
lockWallet.setDisable(true);
|
||||||
searchWallet.disableProperty().bind(exportWallet.disableProperty());
|
searchWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||||
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
||||||
|
@ -792,6 +802,10 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteWallet(ActionEvent event) {
|
||||||
|
deleteWallet(getSelectedWalletForm());
|
||||||
|
}
|
||||||
|
|
||||||
public void closeTab(ActionEvent event) {
|
public void closeTab(ActionEvent event) {
|
||||||
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
}
|
}
|
||||||
|
@ -1851,10 +1865,65 @@ public class AppController implements Initializable {
|
||||||
tabs.getTabs().removeAll(tabs.getTabs());
|
tabs.getTabs().removeAll(tabs.getTabs());
|
||||||
});
|
});
|
||||||
|
|
||||||
contextMenu.getItems().addAll(close, closeOthers, closeAll);
|
MenuItem delete = new MenuItem("Delete...");
|
||||||
|
delete.setOnAction(event -> {
|
||||||
|
deleteWallet(getSelectedWalletForm());
|
||||||
|
});
|
||||||
|
|
||||||
|
contextMenu.getItems().addAll(close, closeOthers, closeAll, delete);
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteWallet(WalletForm selectedWalletForm) {
|
||||||
|
Optional<ButtonType> optButtonType = AppServices.showWarningDialog("Delete Wallet?", "The wallet file and any backups will be deleted. Are you sure?", ButtonType.NO, ButtonType.YES);
|
||||||
|
if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) {
|
||||||
|
Storage storage = selectedWalletForm.getStorage();
|
||||||
|
if(selectedWalletForm.getMasterWallet().isEncrypted()) {
|
||||||
|
WalletPasswordDialog dlg = new WalletPasswordDialog(selectedWalletForm.getWallet().getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
|
if(password.isPresent()) {
|
||||||
|
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(storage, password.get(), true);
|
||||||
|
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new StorageEvent(selectedWalletForm.getWalletId(), TimedEvent.Action.END, "Done"));
|
||||||
|
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
|
deleteStorage(storage);
|
||||||
|
} finally {
|
||||||
|
encryptionFullKey.clear();
|
||||||
|
password.get().clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new StorageEvent(selectedWalletForm.getWalletId(), TimedEvent.Action.END, "Failed"));
|
||||||
|
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
|
||||||
|
Optional<ButtonType> optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK);
|
||||||
|
if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) {
|
||||||
|
Platform.runLater(() -> deleteWallet(getSelectedWalletForm()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("Error deriving wallet key", keyDerivationService.getException());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
EventManager.get().post(new StorageEvent(selectedWalletForm.getWalletId(), TimedEvent.Action.START, "Decrypting wallet..."));
|
||||||
|
keyDerivationService.start();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
|
deleteStorage(storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteStorage(Storage storage) {
|
||||||
|
if(storage.isClosed()) {
|
||||||
|
Platform.runLater(storage::delete);
|
||||||
|
} else {
|
||||||
|
Platform.runLater(() -> deleteStorage(storage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ContextMenu getSubTabContextMenu(Wallet wallet, TabPane subTabs, Tab subTab) {
|
private ContextMenu getSubTabContextMenu(Wallet wallet, TabPane subTabs, Tab subTab) {
|
||||||
ContextMenu contextMenu = new ContextMenu();
|
ContextMenu contextMenu = new ContextMenu();
|
||||||
MenuItem rename = new MenuItem("Rename Account");
|
MenuItem rename = new MenuItem("Rename Account");
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
@ -9,11 +12,14 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.FileSystems;
|
import java.nio.file.FileSystems;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
public class IOUtils {
|
public class IOUtils {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(IOUtils.class);
|
||||||
|
|
||||||
public static FileType getFileType(File file) {
|
public static FileType getFileType(File file) {
|
||||||
try {
|
try {
|
||||||
String type = Files.probeContentType(file.toPath());
|
String type = Files.probeContentType(file.toPath());
|
||||||
|
@ -120,4 +126,28 @@ public class IOUtils {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean secureDelete(File file) {
|
||||||
|
if(file.exists()) {
|
||||||
|
long length = file.length();
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
try(RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
|
||||||
|
raf.seek(0);
|
||||||
|
raf.getFilePointer();
|
||||||
|
byte[] data = new byte[64];
|
||||||
|
int pos = 0;
|
||||||
|
while(pos < length) {
|
||||||
|
random.nextBytes(data);
|
||||||
|
raf.write(data);
|
||||||
|
pos += data.length;
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
log.warn("Error overwriting file for deletion " + file.getName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,6 +305,11 @@ public class JsonPersistence implements Persistence {
|
||||||
com.google.common.io.Files.copy(walletFile, outputStream);
|
com.google.common.io.Files.copy(walletFile, outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
//Nothing required
|
//Nothing required
|
||||||
|
|
|
@ -25,5 +25,6 @@ public interface Persistence {
|
||||||
String getWalletId(Storage storage, Wallet wallet);
|
String getWalletId(Storage storage, Wallet wallet);
|
||||||
String getWalletName(File walletFile, Wallet wallet);
|
String getWalletName(File walletFile, Wallet wallet);
|
||||||
void copyWallet(File walletFile, OutputStream outputStream) throws IOException;
|
void copyWallet(File walletFile, OutputStream outputStream) throws IOException;
|
||||||
|
boolean isClosed();
|
||||||
void close();
|
void close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,10 @@ public class Storage {
|
||||||
return persistence.isPersisted(this, wallet);
|
return persistence.isPersisted(this, wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isClosed() {
|
||||||
|
return persistence.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
ClosePersistenceService closePersistenceService = new ClosePersistenceService();
|
ClosePersistenceService closePersistenceService = new ClosePersistenceService();
|
||||||
closePersistenceService.start();
|
closePersistenceService.start();
|
||||||
|
@ -163,6 +167,11 @@ public class Storage {
|
||||||
persistence.copyWallet(walletFile, outputStream);
|
persistence.copyWallet(walletFile, outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete() {
|
||||||
|
deleteBackups();
|
||||||
|
IOUtils.secureDelete(walletFile);
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteBackups() {
|
public void deleteBackups() {
|
||||||
deleteBackups(null);
|
deleteBackups(null);
|
||||||
}
|
}
|
||||||
|
@ -181,7 +190,7 @@ public class Storage {
|
||||||
private void deleteBackups(String prefix) {
|
private void deleteBackups(String prefix) {
|
||||||
File[] backups = getBackups(prefix);
|
File[] backups = getBackups(prefix);
|
||||||
for(File backup : backups) {
|
for(File backup : backups) {
|
||||||
backup.delete();
|
IOUtils.secureDelete(backup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -568,6 +568,11 @@ public class DbPersistence implements Persistence {
|
||||||
com.google.common.io.Files.copy(walletFile, outputStream);
|
com.google.common.io.Files.copy(walletFile, outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isClosed() {
|
||||||
|
return dataSource.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
EventManager.get().unregister(this);
|
EventManager.get().unregister(this);
|
||||||
|
|
|
@ -48,7 +48,8 @@
|
||||||
<SeparatorMenuItem styleClass="osxHide" />
|
<SeparatorMenuItem styleClass="osxHide" />
|
||||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." accelerator="Shortcut+P" onAction="#openPreferences"/>
|
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." accelerator="Shortcut+P" onAction="#openPreferences"/>
|
||||||
<SeparatorMenuItem />
|
<SeparatorMenuItem />
|
||||||
<MenuItem mnemonicParsing="false" text="Close Tab" accelerator="Shortcut+W" onAction="#closeTab"/>
|
<MenuItem fx:id="deleteWallet" mnemonicParsing="false" text="Delete Wallet..." onAction="#deleteWallet"/>
|
||||||
|
<MenuItem fx:id="closeTab" mnemonicParsing="false" text="Close Tab" accelerator="Shortcut+W" onAction="#closeTab"/>
|
||||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Quit" accelerator="Shortcut+Q" onAction="#quit"/>
|
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Quit" accelerator="Shortcut+Q" onAction="#quit"/>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
Loading…
Reference in a new issue