/**
* Copyright 2012 multibit.org
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.multibit.viewsystem.swing.action;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.crypto.KeyCrypterException;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.file.WalletSaveException;
import org.multibit.message.Message;
import org.multibit.message.MessageManager;
import org.multibit.model.bitcoin.*;
import org.multibit.viewsystem.swing.MultiBitFrame;
import org.multibit.viewsystem.swing.view.panels.SendBitcoinConfirmPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.CharBuffer;
/**
* This {@link Action} actually spends bitcoin.
*/
public class SendBitcoinNowAction extends AbstractAction implements WalletBusyListener {
public Logger log = LoggerFactory.getLogger(SendBitcoinNowAction.class.getName());
private static final long serialVersionUID = 1913592460523457765L;
private final Controller controller;
private final BitcoinController bitcoinController;
private SendBitcoinConfirmPanel sendBitcoinConfirmPanel;
private JPasswordField walletPasswordField;
private final static int MAX_LENGTH_OF_ERROR_MESSAGE = 120;
/**
* Boolean to indicate that the test parameters should be used for "sending".
*/
private boolean useTestParameters = false;
/**
* Boolean to indicate that the "send was successful" or not (when useTestParameters = true).
*/
private boolean sayTestSendWasSuccessful = false;
private Transaction transaction;
private SendRequest sendRequest;
/**
* Creates a new {@link SendBitcoinNowAction}.
*/
public SendBitcoinNowAction(MultiBitFrame mainFrame, BitcoinController bitcoinController,
SendBitcoinConfirmPanel sendBitcoinConfirmPanel, JPasswordField walletPasswordField, ImageIcon icon, SendRequest sendRequest) {
super(bitcoinController.getLocaliser().getString("sendBitcoinConfirmAction.text"), icon);
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
this.sendBitcoinConfirmPanel = sendBitcoinConfirmPanel;
this.walletPasswordField = walletPasswordField;
this.sendRequest = sendRequest;
MnemonicUtil mnemonicUtil = new MnemonicUtil(controller.getLocaliser());
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("sendBitcoinConfirmAction.tooltip"));
putValue(MNEMONIC_KEY, mnemonicUtil.getMnemonic("sendBitcoinConfirmAction.mnemonicKey"));
// This action is a WalletBusyListener.
this.bitcoinController.registerWalletBusyListener(this);
walletBusyChange(this.bitcoinController.getModel().getActivePerWalletModelData().isBusy());
}
/**
* Actually send the bitcoin.
*/
@Override
public void actionPerformed(ActionEvent event) {
sendBitcoinConfirmPanel.setMessageText(" ", " ");
// Check to see if the wallet files have changed.
WalletData perWalletModelData = this.bitcoinController.getModel().getActivePerWalletModelData();
// Put sending message and remove the send button.
sendBitcoinConfirmPanel.setMessageText(controller.getLocaliser().getString("sendBitcoinNowAction.sendingBitcoin"), "");
// Get the label and address out of the wallet preferences.
String sendAddress = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_ADDRESS);
String sendLabel = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_LABEL);
if (sendLabel != null && !sendLabel.equals("")) {
WalletInfoData addressBook = perWalletModelData.getWalletInfo();
addressBook.addSendingAddress(new WalletAddressBookData(sendLabel, sendAddress));
}
char[] walletPassword = walletPasswordField.getPassword();
if (this.bitcoinController.getModel().getActiveWallet() != null
&& this.bitcoinController.getModel().getActiveWallet().getEncryptionType() != EncryptionType.UNENCRYPTED) {
// Encrypted wallet.
if (walletPassword == null || walletPassword.length == 0) {
// User needs to enter password.
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("showExportPrivateKeysAction.youMustEnterTheWalletPassword"), "");
return;
}
try {
if (!this.bitcoinController.getModel().getActiveWallet().checkPassword(CharBuffer.wrap(walletPassword))) {
// The password supplied is incorrect.
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("createNewReceivingAddressSubmitAction.passwordIsIncorrect"),
"");
return;
}
} catch (KeyCrypterException kce) {
log.debug(kce.getClass().getCanonicalName() + " " + kce.getMessage());
// The password supplied is probably incorrect.
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("createNewReceivingAddressSubmitAction.passwordIsIncorrect"), "");
return;
}
}
// Double check wallet is not busy then declare that the active wallet is busy with the task
if (!perWalletModelData.isBusy()) {
perWalletModelData.setBusy(true);
perWalletModelData.setBusyTaskVerbKey("sendBitcoinNowAction.sendingBitcoin");
this.bitcoinController.fireWalletBusyChange(true);
sendBitcoinConfirmPanel.setMessageText(controller.getLocaliser().getString("sendBitcoinNowAction.sendingBitcoin"), "");
sendBitcoinConfirmPanel.invalidate();
sendBitcoinConfirmPanel.validate();
sendBitcoinConfirmPanel.repaint();
performSend(perWalletModelData, sendRequest, CharBuffer.wrap(walletPassword));
}
}
/**
* Send the transaction directly.
*/
private void performSend(WalletData perWalletModelData, SendRequest sendRequest, CharSequence walletPassword) {
String message = null;
boolean sendWasSuccessful = Boolean.FALSE;
try {
if (sendRequest != null && sendRequest.tx != null) {
log.debug("Sending from wallet " + perWalletModelData.getWalletFilename() + ", tx = " + sendRequest.tx.toString());
}
if (useTestParameters) {
log.debug("Using test parameters - not really sending");
if (sayTestSendWasSuccessful) {
sendWasSuccessful = Boolean.TRUE;
log.debug("Using test parameters - saying send was successful");
} else {
message = "test - send failed";
log.debug("Using test parameters - saying send failed");
}
} else {
transaction = this.bitcoinController.getMultiBitService().sendCoins(perWalletModelData, sendRequest, walletPassword);
if (transaction == null) {
// a null transaction returned indicates there was not
// enough money (in spite of our validation)
message = controller.getLocaliser().getString("sendBitcoinNowAction.thereWereInsufficientFundsForTheSend");
log.error(message);
} else {
sendWasSuccessful = Boolean.TRUE;
log.debug("Sent transaction was:\n" + transaction.toString());
}
}
} catch (KeyCrypterException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (WalletSaveException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (IOException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (AddressFormatException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (IllegalStateException e) {
log.error(e.getMessage(), e);
message = controller.getLocaliser().getString("sendBitcoinNowAction.pingFailure");
} catch (Exception e) {
// Really trying to catch anything that goes wrong with the send bitcoin.
log.error(e.getMessage(), e);
message = e.getMessage();
} finally {
// Save the wallet.
try {
this.bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, false);
} catch (WalletSaveException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
}
if (sendWasSuccessful) {
String successMessage = controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSentOk");
if (sendBitcoinConfirmPanel != null && (sendBitcoinConfirmPanel.isVisible() || useTestParameters)) {
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSentOk"));
sendBitcoinConfirmPanel.showOkButton();
sendBitcoinConfirmPanel.clearAfterSend();
} else {
MessageManager.INSTANCE.addMessage(new Message(successMessage));
}
} else {
log.error(message);
if (message != null && message.length() > MAX_LENGTH_OF_ERROR_MESSAGE) {
message = message.substring(0, MAX_LENGTH_OF_ERROR_MESSAGE) + "...";
}
String errorMessage = controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSendFailed");
if (sendBitcoinConfirmPanel != null && (sendBitcoinConfirmPanel.isVisible() || useTestParameters)) {
sendBitcoinConfirmPanel.setMessageText(errorMessage, message);
} else {
MessageManager.INSTANCE.addMessage(new Message(errorMessage + " " + message));
}
}
// Declare that wallet is no longer busy with the task.
perWalletModelData.setBusyTaskKey(null);
perWalletModelData.setBusy(false);
this.bitcoinController.fireWalletBusyChange(false);
log.debug("firing fireRecreateAllViews...");
controller.fireRecreateAllViews(false);
log.debug("firing fireRecreateAllViews...done");
}
}
public Transaction getTransaction() {
return transaction;
}
void setTestParameters(boolean useTestParameters, boolean sayTestSendWasSuccessful) {
this.useTestParameters = useTestParameters;
this.sayTestSendWasSuccessful = sayTestSendWasSuccessful;
}
@Override
public void walletBusyChange(boolean newWalletIsBusy) {
// Update the enable status of the action to match the wallet busy status.
if (this.bitcoinController.getModel().getActivePerWalletModelData().isBusy()) {
// Wallet is busy with another operation that may change the private keys - Action is disabled.
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("multiBitSubmitAction.walletIsBusy",
new Object[]{controller.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())}));
setEnabled(false);
} else {
// Enable
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("sendBitcoinConfirmAction.tooltip"));
setEnabled(true);
}
}
}