/** * Copyright 2011 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.Transaction; 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.WalletData; import org.multibit.network.ReplayManager; import org.multibit.network.ReplayTask; import org.multibit.utils.DateUtils; import org.multibit.viewsystem.swing.MultiBitFrame; import org.multibit.viewsystem.swing.view.walletlist.SingleWalletPanel; import org.multibit.viewsystem.swing.view.walletlist.WalletListPanel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; /** * This {@link Action} resets the blockchain and transactions. */ public class ResetTransactionsSubmitAction extends MultiBitSubmitAction { private static final Logger log = LoggerFactory.getLogger(ResetTransactionsSubmitAction.class); private static final long serialVersionUID = 1923492460523457765L; private static final int NUMBER_OF_MILLISECOND_IN_A_SECOND = 1000; private MultiBitFrame mainFrame; /** * Creates a new {@link ResetTransactionsSubmitAction}. */ public ResetTransactionsSubmitAction(BitcoinController bitcoinController, MultiBitFrame mainFrame, Icon icon) { super(bitcoinController, "resetTransactionsSubmitAction.text", "resetTransactionsSubmitAction.tooltip", "resetTransactionsSubmitAction.mnemonicKey", icon); this.mainFrame = mainFrame; } /** * Reset the transactions and replay the blockchain. */ @Override public void actionPerformed(ActionEvent event) { if (abort()) { return; } setEnabled(false); WalletData activePerWalletModelData = super.bitcoinController.getModel().getActivePerWalletModelData(); // Work out the earliest transaction date and save it to the wallet. Date earliestTransactionDate = new Date(DateUtils.nowUtc().getMillis()); Set<Transaction> allTransactions = activePerWalletModelData.getWallet().getTransactions(true); if (allTransactions != null) { for (Transaction transaction : allTransactions) { if (transaction != null) { Date updateTime = transaction.getUpdateTime(); if (updateTime != null && earliestTransactionDate.after(updateTime)) { earliestTransactionDate = updateTime; } } } } Date actualResetDate = earliestTransactionDate; // Look at the earliest key creation time - this is // returned in seconds and is converted to milliseconds. long earliestKeyCreationTime = activePerWalletModelData.getWallet().getEarliestKeyCreationTime() * NUMBER_OF_MILLISECOND_IN_A_SECOND; if (earliestKeyCreationTime != 0 && earliestKeyCreationTime < earliestTransactionDate.getTime()) { earliestTransactionDate = new Date(earliestKeyCreationTime); actualResetDate = earliestTransactionDate; } // Take an extra day off the reset date to ensure the wallet is cleared entirely actualResetDate = new Date (actualResetDate.getTime() - 3600 * 24 * NUMBER_OF_MILLISECOND_IN_A_SECOND); // Number of milliseconds in a day // Remove the transactions from the wallet. activePerWalletModelData.getWallet().clearTransactions(actualResetDate); // Save the wallet without the transactions. try { super.bitcoinController.getFileHandler().savePerWalletModelData(activePerWalletModelData, true); super.bitcoinController.getModel().createWalletTableData(super.bitcoinController, super.bitcoinController.getModel().getActiveWalletFilename()); controller.fireRecreateAllViews(false); } catch (WalletSaveException wse) { log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage()); MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage())); } // Double check wallet is not busy then declare that the active wallet // is busy with the task WalletData perWalletModelData = this.bitcoinController.getModel().getActivePerWalletModelData(); if (!perWalletModelData.isBusy()) { perWalletModelData.setBusy(true); perWalletModelData.setBusyTaskKey("resetTransactionsSubmitAction.text"); perWalletModelData.setBusyTaskVerbKey("resetTransactionsSubmitAction.verb"); super.bitcoinController.fireWalletBusyChange(true); resetTransactionsInBackground(actualResetDate, activePerWalletModelData.getWalletFilename()); } } /** * Reset the transaction in a background Swing worker thread. */ private void resetTransactionsInBackground(final Date resetDate, final String walletFilename) { SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() { private String message = ""; @Override protected Boolean doInBackground() throws Exception { Boolean successMeasure; log.debug("Starting replay from date = " + resetDate); List<WalletData> perWalletModelDataList = new ArrayList<WalletData>(); perWalletModelDataList.add(bitcoinController.getModel().getActivePerWalletModelData()); // Initialise the message in the SingleWalletPanel. if (mainFrame != null) { WalletListPanel walletListPanel = mainFrame.getWalletsView(); if (walletListPanel != null) { SingleWalletPanel singleWalletPanel = walletListPanel.findWalletPanelByFilename(walletFilename); if (singleWalletPanel != null) { singleWalletPanel.setSyncMessage(controller.getLocaliser().getString("resetTransactionsSubmitAction.verb"), Message.NOT_RELEVANT_PERCENTAGE_COMPLETE); } } } ReplayTask replayTask = new ReplayTask(perWalletModelDataList, resetDate, ReplayTask.UNKNOWN_START_HEIGHT); ReplayManager.INSTANCE.offerReplayTask(replayTask); successMeasure = Boolean.TRUE; return successMeasure; } @Override protected void done() { try { Boolean wasSuccessful = get(); if (wasSuccessful != null && wasSuccessful) { log.debug(message); } else { log.error(message); } if (!message.equals("")) { MessageManager.INSTANCE.addMessage(new Message(message)); } } catch (Exception e) { // Not really used but caught so that SwingWorker shuts down cleanly. log.error(e.getClass() + " " + e.getMessage()); } } }; log.debug("Resetting transactions in background SwingWorker thread"); worker.execute(); } }