From 4c0cc738e4c524c9d79238ea8db60846444ec152 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 5 Apr 2016 03:31:22 +0200 Subject: [PATCH] Add rolling backup --- .../bitsquare/common/crypto/KeyStorage.java | 2 + .../io/bitsquare/common/util/Utilities.java | 34 --------- .../io/bitsquare/storage/FileManager.java | 9 +-- .../java/io/bitsquare/storage/FileUtil.java | 73 +++++++++++++++++++ .../java/io/bitsquare/storage/Storage.java | 1 - .../common/crypto/EncryptionTest.java | 4 +- .../io/bitsquare/common/crypto/SigTest.java | 4 +- .../java/io/bitsquare/btc/WalletService.java | 21 +++++- .../content/password/PasswordView.java | 3 + .../crypto/EncryptionServiceTests.java | 4 +- .../p2p/storage/ProtectedDataStorageTest.java | 9 +-- 11 files changed, 109 insertions(+), 55 deletions(-) create mode 100644 common/src/main/java/io/bitsquare/storage/FileUtil.java diff --git a/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java b/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java index 988d16574f..6a290fa346 100644 --- a/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java +++ b/common/src/main/java/io/bitsquare/common/crypto/KeyStorage.java @@ -18,6 +18,7 @@ package io.bitsquare.common.crypto; import com.google.inject.Inject; +import io.bitsquare.storage.FileUtil; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,6 +87,7 @@ public class KeyStorage { } public KeyPair loadKeyPair(KeyEntry keyEntry) { + FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key"); // long now = System.currentTimeMillis(); try { KeyFactory keyFactory = KeyFactory.getInstance(keyEntry.getAlgorithm(), "BC"); diff --git a/common/src/main/java/io/bitsquare/common/util/Utilities.java b/common/src/main/java/io/bitsquare/common/util/Utilities.java index de520a2233..156ca66f14 100644 --- a/common/src/main/java/io/bitsquare/common/util/Utilities.java +++ b/common/src/main/java/io/bitsquare/common/util/Utilities.java @@ -303,17 +303,6 @@ public class Utilities { return result; } - public static void deleteDirectory(File file) throws IOException { - if (file.isDirectory()) { - File[] files = file.listFiles(); - if (files != null) - for (File c : files) - deleteDirectory(c); - } - if (!file.delete()) - throw new FileNotFoundException("Failed to delete file: " + file); - } - private static void printElapsedTime(String msg) { if (!msg.isEmpty()) { msg += " / "; @@ -348,29 +337,6 @@ public class Utilities { return obj; } - /** - * Empty and delete a folder (and subfolders). - * - * @param folder folder to empty - */ - private static void removeDirectory(final File folder) { - // check if folder file is a real folder - if (folder.isDirectory()) { - File[] list = folder.listFiles(); - if (list != null) { - for (File tmpF : list) { - if (tmpF.isDirectory()) { - removeDirectory(tmpF); - } - if (!tmpF.delete()) - log.warn("can't delete file : " + tmpF); - } - } - if (!folder.delete()) - log.warn("can't delete folder : " + folder); - } - } - public static String readTextFileFromServer(String url, String userAgent) throws IOException { URLConnection connection = URI.create(url).toURL().openConnection(); connection.setDoOutput(true); diff --git a/common/src/main/java/io/bitsquare/storage/FileManager.java b/common/src/main/java/io/bitsquare/storage/FileManager.java index f64b968581..40a50612a4 100644 --- a/common/src/main/java/io/bitsquare/storage/FileManager.java +++ b/common/src/main/java/io/bitsquare/storage/FileManager.java @@ -17,7 +17,6 @@ package io.bitsquare.storage; -import com.google.common.io.Files; import io.bitsquare.common.UserThread; import io.bitsquare.common.util.Utilities; import org.bitcoinj.core.Utils; @@ -162,13 +161,7 @@ public class FileManager { } public synchronized void backupFile(String fileName) throws IOException { - File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); - if (!backupDir.exists()) - if (!backupDir.mkdir()) - log.warn("make dir failed"); - - File backupFile = new File(Paths.get(dir.getAbsolutePath(), "backup", fileName).toString()); - Files.copy(storageFile, backupFile); + FileUtil.rollingBackup(dir, fileName); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/common/src/main/java/io/bitsquare/storage/FileUtil.java b/common/src/main/java/io/bitsquare/storage/FileUtil.java new file mode 100644 index 0000000000..12d2cc7996 --- /dev/null +++ b/common/src/main/java/io/bitsquare/storage/FileUtil.java @@ -0,0 +1,73 @@ +package io.bitsquare.storage; + +import com.google.common.io.Files; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +public class FileUtil { + private static final Logger log = LoggerFactory.getLogger(FileUtil.class); + + public static void rollingBackup(File dir, String fileName) { + File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); + if (!backupDir.exists()) + if (!backupDir.mkdir()) + log.warn("make dir failed"); + + File origFile = new File(Paths.get(dir.getAbsolutePath(), fileName).toString()); + if (origFile.exists()) { + File backupFileDir = new File(Paths.get(backupDir.getAbsolutePath(), fileName.replace(".", "_")).toString()); + if (!backupFileDir.exists()) + if (!backupFileDir.mkdir()) + log.warn("make backupFileDir failed"); + + File backupFile = new File(Paths.get(backupFileDir.getAbsolutePath(), new Date().getTime() + "_" + fileName).toString()); + + try { + Files.copy(origFile, backupFile); + + pruneBackup(backupDir); + } catch (IOException e) { + log.error("Backup key failed " + e.getMessage()); + e.printStackTrace(); + } + } + } + + private static void pruneBackup(File backupDir) { + if (backupDir.isDirectory()) { + File[] files = backupDir.listFiles(); + if (files != null) { + List filesList = Arrays.asList(files); + if (filesList.size() > 10) { + filesList.sort((o1, o2) -> o1.getName().compareTo(o2.getName())); + File file = filesList.get(0); + if (file.isFile()) { + if (!file.delete()) + log.error("Failed to delete file: " + file); + } else { + pruneBackup(new File(Paths.get(backupDir.getAbsolutePath(), file.getName()).toString())); + } + } + } + } + } + + public static void deleteDirectory(File file) throws IOException { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) + for (File c : files) + deleteDirectory(c); + } + if (!file.delete()) + throw new FileNotFoundException("Failed to delete file: " + file); + } +} diff --git a/common/src/main/java/io/bitsquare/storage/Storage.java b/common/src/main/java/io/bitsquare/storage/Storage.java index 4543b3ddef..893271511e 100644 --- a/common/src/main/java/io/bitsquare/storage/Storage.java +++ b/common/src/main/java/io/bitsquare/storage/Storage.java @@ -131,7 +131,6 @@ public class Storage { } } - public void remove(String fileName) { fileManager.removeFile(fileName); } diff --git a/common/src/test/java/io/bitsquare/common/crypto/EncryptionTest.java b/common/src/test/java/io/bitsquare/common/crypto/EncryptionTest.java index 7be39a9f24..39b8212cdd 100644 --- a/common/src/test/java/io/bitsquare/common/crypto/EncryptionTest.java +++ b/common/src/test/java/io/bitsquare/common/crypto/EncryptionTest.java @@ -1,6 +1,6 @@ package io.bitsquare.common.crypto; -import io.bitsquare.common.util.Utilities; +import io.bitsquare.storage.FileUtil; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.After; import org.junit.Before; @@ -37,7 +37,7 @@ public class EncryptionTest { @After public void tearDown() throws IOException { - Utilities.deleteDirectory(dir); + FileUtil.deleteDirectory(dir); } diff --git a/common/src/test/java/io/bitsquare/common/crypto/SigTest.java b/common/src/test/java/io/bitsquare/common/crypto/SigTest.java index 0cda4fc037..158d8b31ee 100644 --- a/common/src/test/java/io/bitsquare/common/crypto/SigTest.java +++ b/common/src/test/java/io/bitsquare/common/crypto/SigTest.java @@ -1,6 +1,6 @@ package io.bitsquare.common.crypto; -import io.bitsquare.common.util.Utilities; +import io.bitsquare.storage.FileUtil; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.After; import org.junit.Before; @@ -35,7 +35,7 @@ public class SigTest { @After public void tearDown() throws IOException { - Utilities.deleteDirectory(dir); + FileUtil.deleteDirectory(dir); } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 3a7d77b92b..d29f1e52be 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -30,6 +30,7 @@ import io.bitsquare.common.UserThread; import io.bitsquare.common.handlers.ErrorMessageHandler; import io.bitsquare.common.handlers.ExceptionHandler; import io.bitsquare.common.handlers.ResultHandler; +import io.bitsquare.storage.FileUtil; import io.bitsquare.user.Preferences; import javafx.beans.property.*; import org.bitcoinj.core.*; @@ -48,8 +49,10 @@ import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import java.io.File; +import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; @@ -125,6 +128,9 @@ public class WalletService { exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " + STARTUP_TIMEOUT_SEC + " seconds.")), STARTUP_TIMEOUT_SEC); + + backupWallet(); + // If seed is non-null it means we are restoring from backup. walletAppKit = new WalletAppKit(params, walletDir, "Bitsquare") { @Override @@ -275,6 +281,19 @@ public class WalletService { }, "RestoreWallet-%d").start(); } + public void backupWallet() { + FileUtil.rollingBackup(walletDir, "Bitsquare.wallet"); + } + + public void clearBackup() { + try { + FileUtil.deleteDirectory(new File(Paths.get(walletDir.getAbsolutePath(), "backup").toString())); + } catch (IOException e) { + log.error("Could not delete directory " + e.getMessage()); + e.printStackTrace(); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Listener @@ -502,7 +521,7 @@ public class WalletService { public Coin getBalanceForAddressEntryWithTradeId(String tradeId) { return getBalanceForAddress(getTradeAddressEntry(tradeId).getAddress()); } - + private Coin getBalance(List transactionOutputs, Address address) { Coin balance = Coin.ZERO; for (TransactionOutput transactionOutput : transactionOutputs) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/content/password/PasswordView.java b/gui/src/main/java/io/bitsquare/gui/main/account/content/password/PasswordView.java index 0325219ea8..31e0a78b93 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/content/password/PasswordView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/content/password/PasswordView.java @@ -118,6 +118,7 @@ public class PasswordView extends ActivatableView { .show(); passwordField.setText(""); repeatedPasswordField.setText(""); + walletService.backupWallet(); } else { new Popup() .warning("You entered the wrong password.\n\n" + @@ -133,6 +134,8 @@ public class PasswordView extends ActivatableView { .show(); passwordField.setText(""); repeatedPasswordField.setText(""); + walletService.clearBackup(); + walletService.backupWallet(); } setText(); }); diff --git a/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java b/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java index 1a63b6bd12..96b1a25f45 100644 --- a/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java +++ b/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java @@ -20,10 +20,10 @@ package io.bitsquare.crypto; import io.bitsquare.app.Version; import io.bitsquare.common.crypto.*; -import io.bitsquare.common.util.Utilities; import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.messaging.MailboxMessage; import io.bitsquare.p2p.messaging.PrefixedSealedAndSignedMessage; +import io.bitsquare.storage.FileUtil; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.After; import org.junit.Before; @@ -66,7 +66,7 @@ public class EncryptionServiceTests { @After public void tearDown() throws IOException { - Utilities.deleteDirectory(dir); + FileUtil.deleteDirectory(dir); } @Test diff --git a/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java b/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java index 8af9b689f5..6f201853c6 100644 --- a/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java +++ b/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java @@ -2,7 +2,6 @@ package io.bitsquare.p2p.storage; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.*; -import io.bitsquare.common.util.Utilities; import io.bitsquare.crypto.EncryptionService; import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.P2PService; @@ -12,6 +11,7 @@ import io.bitsquare.p2p.peers.PeerManager; import io.bitsquare.p2p.storage.messages.RefreshTTLMessage; import io.bitsquare.p2p.storage.mocks.MockData; import io.bitsquare.p2p.storage.storageentry.ProtectedStorageEntry; +import io.bitsquare.storage.FileUtil; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.*; import org.slf4j.Logger; @@ -90,10 +90,9 @@ public class ProtectedDataStorageTest { Path path = Paths.get(TestUtils.test_dummy_dir); File dir = path.toFile(); - Utilities.deleteDirectory(dir); - - Utilities.deleteDirectory(dir1); - Utilities.deleteDirectory(dir2); + FileUtil.deleteDirectory(dir); + FileUtil.deleteDirectory(dir1); + FileUtil.deleteDirectory(dir2); } //@Test