From b96b133c3142e479826dab95b1d0e135ac79ae66 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Tue, 24 Mar 2015 21:10:35 +0100 Subject: [PATCH] Use delayed write on background thread for persistence --- .../java/io/bitsquare/app/BitsquareApp.java | 10 +- .../io/bitsquare/app/BitsquareAppModule.java | 14 +- .../bitsquare/app/BitsquareEnvironment.java | 6 +- .../io/bitsquare/arbitration/Arbitrator.java | 4 +- .../io/bitsquare/btc/AddressEntryList.java | 4 +- .../java/io/bitsquare/btc/WalletService.java | 7 +- .../java/io/bitsquare/gui/Navigation.java | 4 +- .../java/io/bitsquare/gui/main/MainView.java | 5 +- .../io/bitsquare/gui/main/MainViewModel.java | 12 +- .../browser/ArbitratorBrowserView.java | 5 +- .../content/fiat/FiatAccountDataModel.java | 5 +- .../content/irc/IrcAccountDataModel.java | 12 +- .../registration/RegistrationDataModel.java | 5 +- .../restrictions/RestrictionsDataModel.java | 12 +- .../createoffer/CreateOfferDataModel.java | 22 +- .../trade/createoffer/CreateOfferView.java | 2 +- .../createoffer/CreateOfferViewModel.java | 4 +- .../trade/takeoffer/TakeOfferDataModel.java | 16 +- .../main/trade/takeoffer/TakeOfferView.java | 2 +- .../trade/takeoffer/TakeOfferViewModel.java | 4 +- .../io/bitsquare/persistence/Persistence.java | 299 ----------------- .../io/bitsquare/persistence/Storage.java | 103 ------ .../io/bitsquare/storage/FileManager.java | 302 ++++++++++++++++++ .../java/io/bitsquare/storage/Storage.java | 141 ++++++++ .../java/io/bitsquare/trade/TradeManager.java | 73 ++--- .../java/io/bitsquare/trade/TradesList.java | 72 +++++ .../protocol/trade/SharedTradeModel.java | 6 +- .../offerer/models/OffererAsBuyerModel.java | 30 +- .../taker/models/TakerAsSellerModel.java | 36 ++- .../io/bitsquare/user/AccountSettings.java | 7 +- .../java/io/bitsquare/user/Preferences.java | 18 +- .../src/main/java/io/bitsquare/user/User.java | 4 +- .../main/java/io/bitsquare/util/FileUtil.java | 136 -------- .../createoffer/CreateOfferViewModelTest.java | 2 +- .../placeoffer/PlaceOfferProtocolTest.java | 28 +- 35 files changed, 635 insertions(+), 777 deletions(-) delete mode 100644 core/src/main/java/io/bitsquare/persistence/Persistence.java delete mode 100644 core/src/main/java/io/bitsquare/persistence/Storage.java create mode 100644 core/src/main/java/io/bitsquare/storage/FileManager.java create mode 100644 core/src/main/java/io/bitsquare/storage/Storage.java create mode 100644 core/src/main/java/io/bitsquare/trade/TradesList.java delete mode 100644 core/src/main/java/io/bitsquare/util/FileUtil.java diff --git a/core/src/main/java/io/bitsquare/app/BitsquareApp.java b/core/src/main/java/io/bitsquare/app/BitsquareApp.java index 96036729aa..ea3dbc1272 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -26,7 +26,6 @@ import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.debug.DebugView; import io.bitsquare.gui.util.ImageUtil; -import io.bitsquare.persistence.Persistence; import io.bitsquare.util.Utilities; import com.google.common.base.Throwables; @@ -74,20 +73,13 @@ public class BitsquareApp extends Application { bitsquareAppModule = new BitsquareAppModule(env, primaryStage); injector = Guice.createInjector(bitsquareAppModule); injector.getInstance(InjectorViewFactory.class).setInjector(injector); - + // route uncaught exceptions to a user-facing dialog - Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable))); - // load and apply any stored settings - - - Persistence persistence = injector.getInstance(Persistence.class); - persistence.init(); // load the main view and create the main scene - log.trace("viewLoader.load(MainView.class)"); CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class); View view = viewLoader.load(MainView.class); diff --git a/core/src/main/java/io/bitsquare/app/BitsquareAppModule.java b/core/src/main/java/io/bitsquare/app/BitsquareAppModule.java index 350b5008a8..4603caba79 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareAppModule.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareAppModule.java @@ -23,12 +23,11 @@ import io.bitsquare.arbitration.tomp2p.TomP2PArbitratorModule; import io.bitsquare.btc.BitcoinModule; import io.bitsquare.crypto.CryptoModule; import io.bitsquare.gui.GuiModule; -import io.bitsquare.p2p.P2PModule; -import io.bitsquare.p2p.tomp2p.TomP2PModule; import io.bitsquare.offer.OfferModule; import io.bitsquare.offer.tomp2p.TomP2POfferModule; -import io.bitsquare.persistence.Persistence; -import io.bitsquare.persistence.Storage; +import io.bitsquare.p2p.P2PModule; +import io.bitsquare.p2p.tomp2p.TomP2PModule; +import io.bitsquare.storage.Storage; import io.bitsquare.trade.TradeModule; import io.bitsquare.user.AccountSettings; import io.bitsquare.user.Preferences; @@ -63,14 +62,9 @@ class BitsquareAppModule extends BitsquareModule { File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY)); bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir); - File persistenceDir = new File(env.getRequiredProperty(Persistence.DIR_KEY)); - bind(File.class).annotatedWith(named(Persistence.DIR_KEY)).toInstance(persistenceDir); - bindConstant().annotatedWith(named(Persistence.PREFIX_KEY)).to(env.getRequiredProperty(Persistence.PREFIX_KEY)); - bind(Persistence.class).in(Singleton.class); - bind(Environment.class).toInstance(env); bind(UpdateProcess.class).in(Singleton.class); - + install(networkModule()); install(bitcoinModule()); install(cryptoModule()); diff --git a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java index 4221046164..fd17201776 100644 --- a/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java +++ b/core/src/main/java/io/bitsquare/app/BitsquareEnvironment.java @@ -21,8 +21,7 @@ import io.bitsquare.BitsquareException; import io.bitsquare.btc.UserAgent; import io.bitsquare.btc.WalletService; import io.bitsquare.gui.main.MainView; -import io.bitsquare.persistence.Persistence; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import io.bitsquare.util.Utilities; import io.bitsquare.util.spring.JOptCommandLinePropertySource; @@ -137,9 +136,6 @@ public class BitsquareEnvironment extends StandardEnvironment { setProperty(Storage.DIR_KEY, Paths.get(appDataDir, "db").toString()); - setProperty(Persistence.DIR_KEY, appDataDir); - setProperty(Persistence.PREFIX_KEY, appName + "_pref"); - setProperty(MainView.TITLE_KEY, appName); }}); } diff --git a/core/src/main/java/io/bitsquare/arbitration/Arbitrator.java b/core/src/main/java/io/bitsquare/arbitration/Arbitrator.java index ce65addf92..87e9668a64 100644 --- a/core/src/main/java/io/bitsquare/arbitration/Arbitrator.java +++ b/core/src/main/java/io/bitsquare/arbitration/Arbitrator.java @@ -18,7 +18,7 @@ package io.bitsquare.arbitration; import io.bitsquare.locale.LanguageUtil; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import io.bitsquare.user.User; import org.bitcoinj.core.Coin; @@ -95,7 +95,7 @@ public class Arbitrator implements Serializable { public Arbitrator(Storage storage, User user) { this.storage = storage; - Arbitrator persisted = storage.getPersisted(this); + Arbitrator persisted = storage.initAndGetPersisted(this); if (persisted != null) { //TODO for mock arbitrator id = persisted.getName(); diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java index 56b1d6997e..efe51e9701 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java @@ -17,7 +17,7 @@ package io.bitsquare.btc; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import org.bitcoinj.core.Wallet; import org.bitcoinj.crypto.DeterministicKey; @@ -48,7 +48,7 @@ public class AddressEntryList extends ArrayList implements Seriali public void init(Wallet wallet) { this.wallet = wallet; - AddressEntryList persisted = storage.getPersisted(this); + AddressEntryList persisted = storage.initAndGetPersisted(this); if (persisted != null) { for (AddressEntry addressEntry : persisted) { addressEntry.setDeterministicKey((DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash())); diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 76acee872d..e4c3ae4854 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -21,7 +21,6 @@ import io.bitsquare.btc.listeners.AddressConfidenceListener; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.TxConfidenceListener; import io.bitsquare.crypto.SignatureService; -import io.bitsquare.persistence.Persistence; import org.bitcoinj.core.AbstractWalletEventListener; import org.bitcoinj.core.Address; @@ -91,11 +90,9 @@ public class WalletService { private final NetworkParameters params; private final FeePolicy feePolicy; private final SignatureService signatureService; - private final Persistence persistence; private final File walletDir; private final String walletPrefix; private final UserAgent userAgent; - private final BitcoinNetwork bitcoinNetwork; private WalletAppKit walletAppKit; private Wallet wallet; @@ -111,14 +108,12 @@ public class WalletService { @Inject public WalletService(BitcoinNetwork bitcoinNetwork, FeePolicy feePolicy, SignatureService signatureService, - Persistence persistence, AddressEntryList addressEntryList, UserAgent userAgent, + AddressEntryList addressEntryList, UserAgent userAgent, @Named(DIR_KEY) File walletDir, @Named(PREFIX_KEY) String walletPrefix) { - this.bitcoinNetwork = bitcoinNetwork; this.addressEntryList = addressEntryList; this.params = bitcoinNetwork.getParameters(); this.feePolicy = feePolicy; this.signatureService = signatureService; - this.persistence = persistence; this.walletDir = walletDir; this.walletPrefix = walletPrefix; this.userAgent = userAgent; diff --git a/core/src/main/java/io/bitsquare/gui/Navigation.java b/core/src/main/java/io/bitsquare/gui/Navigation.java index 67460646f1..4354500fe1 100644 --- a/core/src/main/java/io/bitsquare/gui/Navigation.java +++ b/core/src/main/java/io/bitsquare/gui/Navigation.java @@ -21,7 +21,7 @@ import io.bitsquare.common.viewfx.view.View; import io.bitsquare.common.viewfx.view.ViewPath; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.trade.BuyView; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import com.google.inject.Inject; @@ -62,7 +62,7 @@ public class Navigation implements Serializable { public Navigation(Storage storage) { this.storage = storage; - Navigation persisted = storage.getPersisted(this); + Navigation persisted = storage.initAndGetPersisted(this); if (persisted != null) { previousPath = persisted.getPreviousPath(); } diff --git a/core/src/main/java/io/bitsquare/gui/main/MainView.java b/core/src/main/java/io/bitsquare/gui/main/MainView.java index 89f501123f..53df27601b 100644 --- a/core/src/main/java/io/bitsquare/gui/main/MainView.java +++ b/core/src/main/java/io/bitsquare/gui/main/MainView.java @@ -68,9 +68,8 @@ public class MainView extends InitializableView { private final String title; @Inject - public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager - overlayManager, - Transitions transitions, @Named(MainView.TITLE_KEY) String title) { + public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager overlayManager, Transitions transitions, + @Named(MainView.TITLE_KEY) String title) { super(model); this.viewLoader = viewLoader; this.navigation = navigation; diff --git a/core/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/core/src/main/java/io/bitsquare/gui/main/MainViewModel.java index 7653926f9b..ae96b8152a 100644 --- a/core/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -18,7 +18,6 @@ package io.bitsquare.gui.main; import io.bitsquare.app.UpdateProcess; -import io.bitsquare.arbitration.ArbitratorService; import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.WalletService; import io.bitsquare.common.viewfx.model.ViewModel; @@ -29,10 +28,8 @@ import io.bitsquare.locale.CountryUtil; import io.bitsquare.p2p.BaseP2PService; import io.bitsquare.p2p.BootstrapState; import io.bitsquare.p2p.ClientNode; -import io.bitsquare.persistence.Persistence; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; -import io.bitsquare.user.AccountSettings; import io.bitsquare.user.User; import com.google.inject.Inject; @@ -91,27 +88,20 @@ class MainViewModel implements ViewModel { private final User user; private final WalletService walletService; private final ClientNode clientNode; - private ArbitratorService arbitratorService; private final TradeManager tradeManager; private UpdateProcess updateProcess; private final BSFormatter formatter; - private Persistence persistence; - private AccountSettings accountSettings; @Inject public MainViewModel(User user, WalletService walletService, ClientNode clientNode, - ArbitratorService arbitratorService, TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess, - BSFormatter formatter, Persistence persistence, AccountSettings accountSettings) { + BSFormatter formatter) { this.user = user; this.walletService = walletService; this.clientNode = clientNode; - this.arbitratorService = arbitratorService; this.tradeManager = tradeManager; this.updateProcess = updateProcess; this.formatter = formatter; - this.persistence = persistence; - this.accountSettings = accountSettings; bitcoinNetworkAsString = bitcoinNetwork.toString(); diff --git a/core/src/main/java/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.java b/core/src/main/java/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.java index c9350194bf..02e169a393 100644 --- a/core/src/main/java/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.java +++ b/core/src/main/java/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.java @@ -27,7 +27,6 @@ import io.bitsquare.common.viewfx.view.View; import io.bitsquare.common.viewfx.view.ViewLoader; import io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView; import io.bitsquare.locale.LanguageUtil; -import io.bitsquare.persistence.Persistence; import io.bitsquare.user.AccountSettings; import java.util.ArrayList; @@ -54,15 +53,13 @@ public class ArbitratorBrowserView extends ActivatableView implement private final ViewLoader viewLoader; private final AccountSettings accountSettings; - private final Persistence persistence; private final ArbitratorService messageService; @Inject - public ArbitratorBrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings, Persistence persistence, + public ArbitratorBrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings, ArbitratorService messageService) { this.viewLoader = viewLoader; this.accountSettings = accountSettings; - this.persistence = persistence; this.messageService = messageService; } diff --git a/core/src/main/java/io/bitsquare/gui/main/account/content/fiat/FiatAccountDataModel.java b/core/src/main/java/io/bitsquare/gui/main/account/content/fiat/FiatAccountDataModel.java index c037e7dd2d..5bf3cd23e4 100644 --- a/core/src/main/java/io/bitsquare/gui/main/account/content/fiat/FiatAccountDataModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/account/content/fiat/FiatAccountDataModel.java @@ -25,7 +25,6 @@ import io.bitsquare.locale.Country; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.Region; -import io.bitsquare.persistence.Persistence; import io.bitsquare.user.AccountSettings; import io.bitsquare.user.User; @@ -46,7 +45,6 @@ class FiatAccountDataModel implements Activatable, DataModel { private final User user; private final AccountSettings accountSettings; - private final Persistence persistence; final StringProperty title = new SimpleStringProperty(); final StringProperty holderName = new SimpleStringProperty(); @@ -68,8 +66,7 @@ class FiatAccountDataModel implements Activatable, DataModel { @Inject - public FiatAccountDataModel(User user, Persistence persistence, AccountSettings accountSettings) { - this.persistence = persistence; + public FiatAccountDataModel(User user, AccountSettings accountSettings) { this.user = user; this.accountSettings = accountSettings; } diff --git a/core/src/main/java/io/bitsquare/gui/main/account/content/irc/IrcAccountDataModel.java b/core/src/main/java/io/bitsquare/gui/main/account/content/irc/IrcAccountDataModel.java index a1d9d44c97..aa26bc11f4 100644 --- a/core/src/main/java/io/bitsquare/gui/main/account/content/irc/IrcAccountDataModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/account/content/irc/IrcAccountDataModel.java @@ -17,15 +17,12 @@ package io.bitsquare.gui.main.account.content.irc; -import io.bitsquare.arbitration.ArbitratorService; import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.fiat.FiatAccount; import io.bitsquare.fiat.FiatAccountType; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CurrencyUtil; -import io.bitsquare.persistence.Persistence; -import io.bitsquare.user.AccountSettings; import io.bitsquare.user.User; import com.google.inject.Inject; @@ -42,9 +39,6 @@ import javafx.collections.ObservableList; class IrcAccountDataModel implements Activatable, DataModel { private final User user; - private final AccountSettings accountSettings; - private final ArbitratorService messageService; - private final Persistence persistence; final StringProperty nickName = new SimpleStringProperty(); final ObjectProperty type = new SimpleObjectProperty<>(); @@ -57,12 +51,8 @@ class IrcAccountDataModel implements Activatable, DataModel { @Inject - public IrcAccountDataModel(User user, Persistence persistence, AccountSettings accountSettings, - ArbitratorService messageService) { - this.persistence = persistence; + public IrcAccountDataModel(User user) { this.user = user; - this.accountSettings = accountSettings; - this.messageService = messageService; } @Override diff --git a/core/src/main/java/io/bitsquare/gui/main/account/content/registration/RegistrationDataModel.java b/core/src/main/java/io/bitsquare/gui/main/account/content/registration/RegistrationDataModel.java index 8edea8a912..8a6bfa1e20 100644 --- a/core/src/main/java/io/bitsquare/gui/main/account/content/registration/RegistrationDataModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/account/content/registration/RegistrationDataModel.java @@ -22,7 +22,6 @@ import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.viewfx.model.DataModel; -import io.bitsquare.persistence.Persistence; import io.bitsquare.user.User; import org.bitcoinj.core.Coin; @@ -50,7 +49,6 @@ class RegistrationDataModel implements DataModel { private final WalletService walletService; private final User user; - private final Persistence persistence; private String transactionId; private AddressEntry addressEntry; @@ -61,11 +59,10 @@ class RegistrationDataModel implements DataModel { @Inject - public RegistrationDataModel(WalletService walletService, User user, Persistence persistence) { + public RegistrationDataModel(WalletService walletService, User user) { this.walletService = walletService; this.user = user; - this.persistence = persistence; if (walletService != null && walletService.getWallet() != null) { addressEntry = walletService.getRegistrationAddressEntry(); diff --git a/core/src/main/java/io/bitsquare/gui/main/account/content/restrictions/RestrictionsDataModel.java b/core/src/main/java/io/bitsquare/gui/main/account/content/restrictions/RestrictionsDataModel.java index 1cd9d71747..cff2e5a6d4 100644 --- a/core/src/main/java/io/bitsquare/gui/main/account/content/restrictions/RestrictionsDataModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/account/content/restrictions/RestrictionsDataModel.java @@ -18,16 +18,13 @@ package io.bitsquare.gui.main.account.content.restrictions; import io.bitsquare.arbitration.Arbitrator; -import io.bitsquare.arbitration.ArbitratorService; import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.locale.Country; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.LanguageUtil; import io.bitsquare.locale.Region; -import io.bitsquare.persistence.Persistence; import io.bitsquare.user.AccountSettings; -import io.bitsquare.user.User; import com.google.inject.Inject; @@ -38,10 +35,7 @@ import javafx.collections.ObservableList; class RestrictionsDataModel implements Activatable, DataModel { - private final User user; private final AccountSettings accountSettings; - private final Persistence persistence; - private final ArbitratorService messageService; final ObservableList languageList = FXCollections.observableArrayList(); final ObservableList countryList = FXCollections.observableArrayList(); @@ -52,12 +46,8 @@ class RestrictionsDataModel implements Activatable, DataModel { @Inject - public RestrictionsDataModel(User user, AccountSettings accountSettings, Persistence persistence, - ArbitratorService messageService) { - this.user = user; + public RestrictionsDataModel(AccountSettings accountSettings) { this.accountSettings = accountSettings; - this.persistence = persistence; - this.messageService = messageService; } @Override diff --git a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferDataModel.java b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferDataModel.java index 5dd5841754..22e7c573c7 100644 --- a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferDataModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferDataModel.java @@ -28,7 +28,6 @@ import io.bitsquare.fiat.FiatAccount; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.locale.Country; import io.bitsquare.offer.Direction; -import io.bitsquare.persistence.Persistence; import io.bitsquare.trade.TradeManager; import io.bitsquare.user.AccountSettings; import io.bitsquare.user.Preferences; @@ -72,8 +71,6 @@ class CreateOfferDataModel implements Activatable, DataModel { private final WalletService walletService; private final AccountSettings accountSettings; private Preferences preferences; - private final User user; - private final Persistence persistence; private final BSFormatter formatter; private final String offerId; @@ -110,14 +107,12 @@ class CreateOfferDataModel implements Activatable, DataModel { // non private for testing @Inject public CreateOfferDataModel(TradeManager tradeManager, WalletService walletService, AccountSettings accountSettings, - Preferences preferences, User user, Persistence persistence, + Preferences preferences, User user, BSFormatter formatter) { this.tradeManager = tradeManager; this.walletService = walletService; this.accountSettings = accountSettings; this.preferences = preferences; - this.user = user; - this.persistence = persistence; this.formatter = formatter; this.offerId = UUID.randomUUID().toString(); @@ -231,7 +226,7 @@ class CreateOfferDataModel implements Activatable, DataModel { } void securityDepositInfoDisplayed() { - persistence.write("displaySecurityDepositInfo", false); + preferences.setDisplaySecurityDepositInfo(false); } @@ -255,15 +250,6 @@ class CreateOfferDataModel implements Activatable, DataModel { return offerId; } - Boolean displaySecurityDepositInfo() { - Object securityDepositInfoDisplayedObject = persistence.read("displaySecurityDepositInfo"); - if (securityDepositInfoDisplayedObject instanceof Boolean) - return (Boolean) securityDepositInfoDisplayedObject; - else - return true; - } - - private void updateBalance(@NotNull Coin balance) { isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); } @@ -281,4 +267,8 @@ class CreateOfferDataModel implements Activatable, DataModel { fiatCode.set(fiatAccount.getCurrency().getCurrencyCode()); } } + + public Boolean getDisplaySecurityDepositInfo() { + return preferences.getDisplaySecurityDepositInfo(); + } } diff --git a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferView.java b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferView.java index 3501b7f0b7..48adfc63e1 100644 --- a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferView.java +++ b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferView.java @@ -147,7 +147,7 @@ public class CreateOfferView extends ActivatableViewAndModel actions = new ArrayList<>(); actions.add(new AbstractAction(BSResources.get("shared.close")) { diff --git a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModel.java b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModel.java index aa5dd0f01e..3658fcc599 100644 --- a/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModel.java @@ -250,8 +250,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel actions = new ArrayList<>(); actions.add(new AbstractAction(BSResources.get("shared.close")) { diff --git a/core/src/main/java/io/bitsquare/gui/main/trade/takeoffer/TakeOfferViewModel.java b/core/src/main/java/io/bitsquare/gui/main/trade/takeoffer/TakeOfferViewModel.java index 1bc474de43..528e319eba 100644 --- a/core/src/main/java/io/bitsquare/gui/main/trade/takeoffer/TakeOfferViewModel.java +++ b/core/src/main/java/io/bitsquare/gui/main/trade/takeoffer/TakeOfferViewModel.java @@ -359,8 +359,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel im return paymentLabel; } - Boolean displaySecurityDepositInfo() { - return dataModel.displaySecurityDepositInfo(); + Boolean getDisplaySecurityDepositInfo() { + return dataModel.getDisplaySecurityDepositInfo(); } diff --git a/core/src/main/java/io/bitsquare/persistence/Persistence.java b/core/src/main/java/io/bitsquare/persistence/Persistence.java deleted file mode 100644 index e5775d2d05..0000000000 --- a/core/src/main/java/io/bitsquare/persistence/Persistence.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * This file is part of Bitsquare. - * - * Bitsquare is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bitsquare is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bitsquare. If not, see . - */ - -package io.bitsquare.persistence; - -import org.bitcoinj.core.Utils; -import org.bitcoinj.utils.Threading; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.ReentrantLock; - -import javax.annotation.concurrent.GuardedBy; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Simple storage solution for serialized data - * TODO: Should be improved with a more robust solution or maybe a lightweight database. - * TODO: Should run in a dedicated thread. - */ -public class Persistence { - private static final Logger log = LoggerFactory.getLogger(Persistence.class); - private static final ReentrantLock lock = Threading.lock("Storage"); - - public static final String DIR_KEY = "persistence.dir"; - public static final String PREFIX_KEY = "persistence.prefix"; - private static final long MIN_INTERVAL_BETWEEN_WRITE_OPERATIONS = 1000; - - @GuardedBy("lock") - private Map rootMap = new HashMap<>(); - private Map timestampMap = new HashMap<>(); - - private final File dir; - private final String prefix; - private final File storageFile; - private int resetCounter = 0; - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - public Persistence( - @Named(DIR_KEY) File dir, - @Named(PREFIX_KEY) String prefix) { - this.dir = dir; - this.prefix = prefix; - this.storageFile = new File(dir, prefix + ".ser"); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public API - /////////////////////////////////////////////////////////////////////////////////////////// - - public void init() { - try { - lock.lock(); - final Map map = readRootMap(); - - if (map == null) - saveObjectToFile((Serializable) rootMap); - else - rootMap = map; - } finally { - lock.unlock(); - } - } - - // Map - public void write(Object classInstance, String propertyKey, List value) { - write(classInstance.getClass().getName() + "." + propertyKey, value); - } - public void write(Object classInstance, String propertyKey, Serializable value) { - write(classInstance.getClass().getName() + "." + propertyKey, value); - } - - // not used outside - public void write(String key, Map value) { - write(key, (Serializable) value); - } - public void write(String key, List value) { - write(key, (Serializable) value); - } - - - // Serializable - public void remove(Object classInstance, String propertyKey) { - try { - lock.lock(); - rootMap.remove(classInstance.getClass().getName() + "." + propertyKey); - saveObjectToFile((Serializable) rootMap); - } finally { - lock.unlock(); - } - } - - - public void write(Serializable classInstance) { - write(classInstance.getClass().getName(), classInstance); - } - - public void write(String key, Serializable value) { - //log.trace("Write object with key = " + key + " / value = " + value); - // TODO add throttle to limit write operations - - try { - lock.lock(); - rootMap.put(key, value); - saveObjectToFile((Serializable) rootMap); - } finally { - lock.unlock(); - } - } - - public Serializable read(Object classInstance) { - return read(classInstance.getClass().getName()); - } - - public Serializable read(Object classInstance, String propertyKey) { - return read(classInstance.getClass().getName() + "." + propertyKey); - } - - // read from local rootMap, just if not found read from disc - public Serializable read(String key) { - try { - lock.lock(); - if (rootMap.containsKey(key)) { - // log.trace("Read object with key = " + key + " / value = " + rootMap.get(key)); - return rootMap.get(key); - } - else { - final Map map = readRootMap(); - if (map != null) { - rootMap = map; - } - if (rootMap.containsKey(key)) { - // log.trace("Read object with key = " + key + " / value = " + rootMap.get(key)); - return rootMap.get(key); - } - else { - log.info("Object with key = " + key + " not found."); - return null; - } - } - } finally { - lock.unlock(); - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private methods - /////////////////////////////////////////////////////////////////////////////////////////// - - - private Map readRootMap() { - try { - final Object object = readObjectFromFile(storageFile); - if (object == null) { - log.error("readRootMap returned null object."); - return null; - } - else { - if (object instanceof Map) { - return (Map) object; - } - else { - log.error("Object is not type of Map"); - return null; - } - } - - } catch (FileNotFoundException e) { - - log.trace("File not found is ok for the first execute."); - return null; - } catch (ClassNotFoundException | IOException e2) { - log.warn("Could not read rootMap. " + e2); - - // If there are problems with incompatible versions, we reset the persisted data - // TODO We need a clean solution when we use another persistence solution - rootMap = new HashMap<>(); - saveObjectToFile((Serializable) rootMap); - resetCounter++; - - // We only try that once, if it fails repeatedly we - if (resetCounter == 1) { - log.warn("We reset the persisted data and try again to read the root map."); - return readRootMap(); - } - else { - e2.printStackTrace(); - log.error("We tried already to reset the persisted data, but we get an error again, so we give up."); - return null; - } - } - } - - private void saveObjectToFile(Serializable serializable) { - File tempFile = null; - FileOutputStream fileOutputStream = null; - ObjectOutputStream objectOutputStream = null; - try { - tempFile = File.createTempFile("temp_" + prefix, null, dir); - - // Don't use auto closeable resources in try() as we would need too many try/catch clauses (for tempFile) - // and we need to close it - // manually before replacing file with temp file - fileOutputStream = new FileOutputStream(tempFile); - objectOutputStream = new ObjectOutputStream(fileOutputStream); - - objectOutputStream.writeObject(serializable); - - // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide - // to not write through to physical media for at least a few seconds, but this is the best we can do. - fileOutputStream.flush(); - fileOutputStream.getFD().sync(); - - // Close resources before replacing file with temp file because otherwise it causes problems on windows - // when rename temp file - fileOutputStream.close(); - objectOutputStream.close(); - - writeTempFileToFile(tempFile, storageFile); - } catch (IOException e) { - e.printStackTrace(); - log.error("save object to file failed." + e); - } finally { - if (tempFile != null && tempFile.exists()) { - log.warn("Temp file still exists after failed save."); - if (!tempFile.delete()) log.error("Cannot delete temp file."); - } - - try { - if (objectOutputStream != null) objectOutputStream.close(); - if (fileOutputStream != null) fileOutputStream.close(); - } catch (IOException e) { - e.printStackTrace(); - log.error("Cannot close resources."); - } - } - } - - private Object readObjectFromFile(File file) throws IOException, ClassNotFoundException { - lock.lock(); - try (final FileInputStream fileInputStream = new FileInputStream(file); - final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { - return objectInputStream.readObject(); - } finally { - lock.unlock(); - } - } - - public void writeTempFileToFile(File tempFile, File file) throws IOException { - if (Utils.isWindows()) { - // Work around an issue on Windows whereby you can't rename over existing files. - final File canonical = file.getCanonicalFile(); - if (canonical.exists() && !canonical.delete()) { - throw new IOException("Failed to delete canonical file for replacement with save"); - } - if (!tempFile.renameTo(canonical)) { - throw new IOException("Failed to rename " + tempFile + " to " + canonical); - } - } - else if (!tempFile.renameTo(file)) { - throw new IOException("Failed to rename " + tempFile + " to " + file); - } - } - -} diff --git a/core/src/main/java/io/bitsquare/persistence/Storage.java b/core/src/main/java/io/bitsquare/persistence/Storage.java deleted file mode 100644 index 6709691949..0000000000 --- a/core/src/main/java/io/bitsquare/persistence/Storage.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of Bitsquare. - * - * Bitsquare is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bitsquare is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bitsquare. If not, see . - */ - -package io.bitsquare.persistence; - -import io.bitsquare.gui.components.Popups; -import io.bitsquare.util.FileUtil; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InvalidClassException; -import java.io.Serializable; - -import java.nio.file.Paths; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Storage { - private static final Logger log = LoggerFactory.getLogger(Storage.class); - - public static final String DIR_KEY = "storage.dir"; - private final File dir; - private File storageFile; - private T serializable; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - public Storage(@Named(DIR_KEY) File dir) { - this.dir = dir; - } - - public void save() { - if (storageFile == null) - throw new RuntimeException("storageFile = null. Call setupFileStorage before using read/write."); - - try { - FileUtil.write(serializable, dir, storageFile); - } catch (IOException e) { - e.printStackTrace(); - log.error(e.getMessage()); - Popups.openErrorPopup("An exception occurred at writing data to disc.", e.getMessage()); - } - } - - public T getPersisted(T serializable) { - this.serializable = serializable; - storageFile = new File(dir, serializable.getClass().getSimpleName() + ".ser"); - - if (storageFile == null) - throw new RuntimeException("storageFile = null. Call init before using read/write."); - - try { - T persistedObject = (T) FileUtil.read(storageFile); - - // If we did not get any exception we can be sure the data are consistent so we make a backup - FileUtil.backupFile(storageFile, new File(Paths.get(dir.getAbsolutePath(), "backups").toString()), - serializable.getClass().getSimpleName() + ".ser"); - return persistedObject; - } catch (InvalidClassException e) { - log.error("Version of persisted class has changed. We cannot read the persisted data anymore. We make a backup and remove the inconsistent file."); - try { - // In case the persisted data have been critical (keys) we keep a backup which might be used for recovery - FileUtil.removeAndBackupFile(storageFile, new File(Paths.get(dir.getAbsolutePath(), "inconsistent").toString()), - serializable.getClass().getSimpleName() + ".ser"); - } catch (IOException e1) { - e1.printStackTrace(); - log.error(e1.getMessage()); - // We swallow Exception if backup fails - } - } catch (FileNotFoundException e) { - log.info("File not available. That is OK for the first run."); - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - log.error(e.getMessage()); - Popups.openErrorPopup("An exception occurred at reading data from disc.", e.getMessage()); - - } - return null; - } -} diff --git a/core/src/main/java/io/bitsquare/storage/FileManager.java b/core/src/main/java/io/bitsquare/storage/FileManager.java new file mode 100644 index 0000000000..6bc8260c67 --- /dev/null +++ b/core/src/main/java/io/bitsquare/storage/FileManager.java @@ -0,0 +1,302 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +/** + * Copyright 2013 Google Inc. + * Copyright 2014 Andreas Schildbach + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 io.bitsquare.storage; + +import io.bitsquare.gui.components.Popups; + +import org.bitcoinj.core.Utils; +import org.bitcoinj.utils.Threading; + +import com.google.common.base.Throwables; +import com.google.common.io.Files; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import java.nio.file.Paths; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import javafx.application.Platform; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Borrowed from BitcoinJ WalletFiles + * A class that handles atomic and optionally delayed writing of a file to disk. + * It can be useful to delay writing of a file to disk on slow devices. + * By coalescing writes and doing serialization + * and disk IO on a background thread performance can be improved. + */ +public class FileManager { + private static final Logger log = LoggerFactory.getLogger(FileManager.class); + private static final ReentrantLock lock = Threading.lock("FileUtil"); + + private final File dir; + private final File storageFile; + private final ScheduledThreadPoolExecutor executor; + private final AtomicBoolean savePending; + private final long delay; + private final TimeUnit delayTimeUnit; + private final Callable saver; + private T serializable; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public FileManager(File dir, File storageFile, long delay, TimeUnit delayTimeUnit) { + this.dir = dir; + this.storageFile = storageFile; + + final ThreadFactoryBuilder builder = new ThreadFactoryBuilder() + .setDaemon(true) + .setNameFormat("FileManager thread") + .setPriority(Thread.MIN_PRIORITY); // Avoid competing with the GUI thread. + + builder.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable throwable) { + Platform.runLater(() -> Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable))); + } + }); + + // An executor that starts up threads when needed and shuts them down later. + this.executor = new ScheduledThreadPoolExecutor(1, builder.build()); + this.executor.setKeepAliveTime(5, TimeUnit.SECONDS); + this.executor.allowCoreThreadTimeOut(true); + this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + + // File must only be accessed from the auto-save executor from now on, to avoid simultaneous access. + this.savePending = new AtomicBoolean(); + this.delay = delay; + this.delayTimeUnit = checkNotNull(delayTimeUnit); + + this.saver = new Callable() { + @Override + public Void call() throws Exception { + // Runs in an auto save thread. + if (!savePending.getAndSet(false)) { + // Some other scheduled request already beat us to it. + return null; + } + saveNowInternal(serializable); + return null; + } + }; + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + FileManager.this.shutdown(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Actually write the wallet file to disk, using an atomic rename when possible. Runs on the current thread. + */ + public void saveNow(T serializable) throws IOException { + saveNowInternal(serializable); + } + + /** + * Queues up a save in the background. Useful for not very important wallet changes. + */ + public void saveLater(T serializable) { + this.serializable = serializable; + + if (savePending.getAndSet(true)) + return; // Already pending. + executor.schedule(saver, delay, delayTimeUnit); + } + + public Object read(File file) throws IOException, ClassNotFoundException { + lock.lock(); + try (final FileInputStream fileInputStream = new FileInputStream(file); + final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { + return objectInputStream.readObject(); + } finally { + lock.unlock(); + } + } + + public void removeFile(T serializable) { + storageFile.delete(); + + File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); + if (backupDir.exists()) { + File backupFile = new File(backupDir, serializable.getClass().getSimpleName()); + if (backupFile.exists()) + backupFile.delete(); + } + } + + + /** + * Shut down auto-saving. + */ + public void shutdown() { + if (serializable != null) + log.debug("shutDown " + serializable.getClass().getSimpleName()); + else + log.debug("shutDown"); + + executor.shutdown(); + try { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); // forever + } catch (InterruptedException x) { + throw new RuntimeException(x); + } + } + + public void removeAndBackupFile(File storageFile, File dir, String name) throws IOException { + File corruptedBackupDir = new File(Paths.get(dir.getAbsolutePath(), "corrupted").toString()); + if (!corruptedBackupDir.exists()) + corruptedBackupDir.mkdir(); + + renameTempFileToFile(storageFile, new File(corruptedBackupDir, serializable.getClass().getSimpleName())); + } + + public void backupFile(T serializable) throws IOException { + File backupDir = new File(Paths.get(dir.getAbsolutePath(), "backup").toString()); + if (!backupDir.exists()) + backupDir.mkdir(); + + Files.copy(storageFile, new File(backupDir, serializable.getClass().getSimpleName())); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void saveNowInternal(T serializable) throws IOException { + long now = System.currentTimeMillis(); + saveToFile(serializable, dir, storageFile); + log.info("Save completed in {}msec", System.currentTimeMillis() - now); + } + + private void saveToFile(T serializable, File dir, File storageFile) throws IOException { + lock.lock(); + File tempFile = null; + FileOutputStream fileOutputStream = null; + ObjectOutputStream objectOutputStream = null; + try { + if (!dir.exists()) + dir.mkdir(); + + tempFile = File.createTempFile("temp", null, dir); + + // Don't use auto closeable resources in try() as we would need too many try/catch clauses (for tempFile) + // and we need to close it + // manually before replacing file with temp file + fileOutputStream = new FileOutputStream(tempFile); + objectOutputStream = new ObjectOutputStream(fileOutputStream); + + objectOutputStream.writeObject(serializable); + + // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide + // to not write through to physical media for at least a few seconds, but this is the best we can do. + fileOutputStream.flush(); + fileOutputStream.getFD().sync(); + + // Close resources before replacing file with temp file because otherwise it causes problems on windows + // when rename temp file + fileOutputStream.close(); + objectOutputStream.close(); + + renameTempFileToFile(tempFile, storageFile); + } finally { + if (tempFile != null && tempFile.exists()) { + log.warn("Temp file still exists after failed save."); + if (!tempFile.delete()) log.error("Cannot delete temp file."); + } + + try { + if (objectOutputStream != null) + objectOutputStream.close(); + if (fileOutputStream != null) + fileOutputStream.close(); + } catch (IOException e) { + // We swallow that + e.printStackTrace(); + log.error("Cannot close resources."); + } + lock.unlock(); + } + } + + private void renameTempFileToFile(File tempFile, File file) throws IOException { + lock.lock(); + try { + if (Utils.isWindows()) { + // Work around an issue on Windows whereby you can't rename over existing files. + final File canonical = file.getCanonicalFile(); + if (canonical.exists() && !canonical.delete()) { + throw new IOException("Failed to delete canonical file for replacement with save"); + } + if (!tempFile.renameTo(canonical)) { + throw new IOException("Failed to rename " + tempFile + " to " + canonical); + } + } + else if (!tempFile.renameTo(file)) { + throw new IOException("Failed to rename " + tempFile + " to " + file); + } + } finally { + lock.unlock(); + } + } +} diff --git a/core/src/main/java/io/bitsquare/storage/Storage.java b/core/src/main/java/io/bitsquare/storage/Storage.java new file mode 100644 index 0000000000..395682a39e --- /dev/null +++ b/core/src/main/java/io/bitsquare/storage/Storage.java @@ -0,0 +1,141 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.storage; + +import io.bitsquare.gui.components.Popups; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InvalidClassException; +import java.io.Serializable; + +import java.nio.file.Paths; + +import java.util.concurrent.TimeUnit; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * That class handles the storage of a particular object to disk using Java serialisation. + * To support evolving versions of the serialised data we need to take care that we don't break the object structure. + * Java serialisation is tolerant with added fields, but removing or changing existing fields will break the backwards compatibility. + * Alternative frameworks for serialisation like Kyro or mapDB have shown problems with version migration, so we stuck with plain Java + * serialisation. + *

+ * For every data object we write a separate file to minimize the risk of corrupted files in case of inconsistency from newer versions. + * In case of a corrupted file we backup the old file to a separate directory, so if it holds critical data it might be helpful for recovery. + *

+ * We also backup at first read the file, so we have a valid file form the latest version in case a write operation corrupted the file. + *

+ * The read operation is triggered just at object creation (startup) and is at the moment not executed on a background thread to avoid asynchronous behaviour. + * As the data are small and it is just one read access the performance penalty is small and might be even worse to create and setup a thread for it. + *

+ * The write operation used a background thread and supports a delayed write to avoid too many repeated write operations. + */ +public class Storage { + private static final Logger log = LoggerFactory.getLogger(Storage.class); + public static final String DIR_KEY = "storage.dir"; + + private final File dir; + private FileManager fileManager; + private File storageFile; + private T serializable; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public Storage(@Named(DIR_KEY) File dir) { + this.dir = dir; + } + + public T initAndGetPersisted(T serializable) { + return initAndGetPersisted(serializable, serializable.getClass().getSimpleName()); + } + + public T initAndGetPersisted(T serializable, String fileName) { + this.serializable = serializable; + storageFile = new File(dir, fileName); + fileManager = new FileManager<>(dir, storageFile, 500, TimeUnit.MILLISECONDS); + + return getPersisted(serializable); + } + + // Save delayed and on a background thread + public void save() { + if (storageFile == null) + throw new RuntimeException("storageFile = null. Call setupFileStorage before using read/write."); + + fileManager.saveLater(serializable); + } + + public void remove() { + fileManager.removeFile(serializable); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + // We do the file read on the UI thread to avoid problems from multi threading. + // Data are small and read is done only at startup, so it is no performance issue. + private T getPersisted(T serializable) { + if (storageFile == null) + throw new RuntimeException("storageFile = null. Call init before using read/write."); + + try { + long now = System.currentTimeMillis(); + T persistedObject = (T) fileManager.read(storageFile); + log.info("Read {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); + + // If we did not get any exception we can be sure the data are consistent so we make a backup + now = System.currentTimeMillis(); + fileManager.backupFile(serializable); + log.info("Backup {} completed in {}msec", serializable.getClass().getSimpleName(), System.currentTimeMillis() - now); + + return persistedObject; + } catch (InvalidClassException e) { + log.error("Version of persisted class has changed. We cannot read the persisted data anymore. We make a backup and remove the inconsistent file."); + try { + // In case the persisted data have been critical (keys) we keep a backup which might be used for recovery + fileManager.removeAndBackupFile(storageFile, new File(Paths.get(dir.getAbsolutePath(), "inconsistent").toString()), + serializable.getClass().getSimpleName()); + } catch (IOException e1) { + e1.printStackTrace(); + log.error(e1.getMessage()); + // We swallow Exception if backup fails + } + } catch (FileNotFoundException e) { + log.info("File not available. That is OK for the first run."); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + log.error(e.getMessage()); + Popups.openErrorPopup("An exception occurred at reading data from disc.", e.getMessage()); + + } + return null; + } +} diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index 8e657d3ce1..4e744e9a3d 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -33,7 +33,6 @@ import io.bitsquare.p2p.MailboxMessage; import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.Peer; -import io.bitsquare.persistence.Persistence; import io.bitsquare.trade.handlers.TradeResultHandler; import io.bitsquare.trade.handlers.TransactionResultHandler; import io.bitsquare.trade.protocol.availability.CheckOfferAvailabilityModel; @@ -51,17 +50,16 @@ import io.bitsquare.user.User; import org.bitcoinj.core.Coin; import org.bitcoinj.utils.Fiat; -import java.io.Serializable; +import java.io.File; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import javax.inject.Inject; +import javax.inject.Named; -import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.slf4j.Logger; @@ -72,7 +70,6 @@ public class TradeManager { private final User user; private final AccountSettings accountSettings; - private final Persistence persistence; private final MessageService messageService; private MailboxService mailboxService; private final AddressService addressService; @@ -81,14 +78,15 @@ public class TradeManager { private final SignatureService signatureService; private EncryptionService encryptionService; private final OfferBookService offerBookService; + private File storageDir; private final Map takerAsSellerProtocolMap = new HashMap<>(); private final Map offererAsBuyerProtocolMap = new HashMap<>(); private final Map checkOfferAvailabilityProtocolMap = new HashMap<>(); - private final ObservableList openOfferTrades = FXCollections.observableArrayList(); - private final ObservableList pendingTrades = FXCollections.observableArrayList(); - private final ObservableList closedTrades = FXCollections.observableArrayList(); + private final TradesList openOfferTrades; + private final TradesList pendingTrades; + private final TradesList closedTrades; private final Map mailboxMessages = new HashMap<>(); @@ -97,13 +95,15 @@ public class TradeManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public TradeManager(User user, AccountSettings accountSettings, Persistence persistence, + public TradeManager(User user, AccountSettings accountSettings, TradesList openOfferTrades, TradesList pendingTrades, TradesList closedTrades, MessageService messageService, MailboxService mailboxService, AddressService addressService, BlockChainService blockChainService, WalletService walletService, SignatureService signatureService, EncryptionService encryptionService, - OfferBookService offerBookService) { + OfferBookService offerBookService, @Named("storage.dir") File storageDir) { this.user = user; this.accountSettings = accountSettings; - this.persistence = persistence; + this.openOfferTrades = openOfferTrades; + this.pendingTrades = pendingTrades; + this.closedTrades = closedTrades; this.messageService = messageService; this.mailboxService = mailboxService; this.addressService = addressService; @@ -112,19 +112,7 @@ public class TradeManager { this.signatureService = signatureService; this.encryptionService = encryptionService; this.offerBookService = offerBookService; - - Serializable openOffersObject = persistence.read(this, "openOffers"); - if (openOffersObject instanceof List) { - openOfferTrades.addAll((List) openOffersObject); - } - Serializable pendingTradesObject = persistence.read(this, "pendingTrades"); - if (pendingTradesObject instanceof List) { - pendingTrades.addAll((List) pendingTradesObject); - } - Serializable closedTradesObject = persistence.read(this, "closedTrades"); - if (closedTradesObject instanceof List) { - closedTrades.addAll((List) closedTradesObject); - } + this.storageDir = storageDir; } @@ -198,7 +186,6 @@ public class TradeManager { Trade trade = new Trade(offer); trade.setLifeCycleState(Trade.LifeCycleState.OPEN_OFFER); openOfferTrades.add(trade); - persistOpenOfferTrades(); createOffererAsBuyerProtocol(trade); resultHandler.handleResult(transaction); @@ -265,7 +252,7 @@ public class TradeManager { // TODO remove if check when persistence is impl. if (offererAsBuyerProtocolMap.containsKey(tradeId)) { offererAsBuyerProtocolMap.get(tradeId).onFiatPaymentStarted(); - persistPendingTrades(); + // persistPendingTrades(); } } @@ -276,9 +263,7 @@ public class TradeManager { public void onWithdrawAtTradeCompleted(Trade trade) { trade.setLifeCycleState(Trade.LifeCycleState.COMPLETED); pendingTrades.remove(trade); - persistPendingTrades(); closedTrades.add(trade); - persistClosedTrades(); removeFromProtocolMap(trade); } @@ -297,15 +282,15 @@ public class TradeManager { /////////////////////////////////////////////////////////////////////////////////////////// public ObservableList getOpenOfferTrades() { - return openOfferTrades; + return openOfferTrades.getObservableList(); } public ObservableList getPendingTrades() { - return pendingTrades; + return pendingTrades.getObservableList(); } public ObservableList getClosedTrades() { - return closedTrades; + return closedTrades.getObservableList(); } @@ -329,12 +314,10 @@ public class TradeManager { if (result.isPresent()) { Trade trade = result.get(); openOfferTrades.remove(trade); - persistOpenOfferTrades(); if (isCancelRequest) { trade.setLifeCycleState(Trade.LifeCycleState.CANCELED); closedTrades.add(trade); - persistClosedTrades(); } } @@ -375,7 +358,6 @@ public class TradeManager { trade.setTradingPeer(peer); trade.setLifeCycleState(Trade.LifeCycleState.PENDING); pendingTrades.add(trade); - persistPendingTrades(); TakerAsSellerProtocol sellerTakesOfferProtocol = createTakerAsSellerProtocol(trade); //trade.setProtocol(sellerTakesOfferProtocol); @@ -397,7 +379,7 @@ public class TradeManager { case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_RECEIVED: case PAYOUT_PUBLISHED: - persistPendingTrades(); + // persistPendingTrades(); break; case MESSAGE_SENDING_FAILED: case FAULT: @@ -418,7 +400,7 @@ public class TradeManager { blockChainService, signatureService, user, - persistence); + storageDir); TakerAsSellerProtocol protocol = new TakerAsSellerProtocol(model); takerAsSellerProtocolMap.put(trade.getId(), protocol); @@ -440,7 +422,7 @@ public class TradeManager { blockChainService, signatureService, user, - persistence); + storageDir); // TODO check, remove listener @@ -450,7 +432,7 @@ public class TradeManager { case INIT: break; case TAKE_OFFER_FEE_TX_CREATED: - persistPendingTrades(); + // persistPendingTrades(); break; case DEPOSIT_PUBLISHED: removeOpenOffer(trade.getOffer(), @@ -459,13 +441,12 @@ public class TradeManager { false); model.trade.setLifeCycleState(Trade.LifeCycleState.PENDING); pendingTrades.add(trade); - persistPendingTrades(); break; case DEPOSIT_CONFIRMED: case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_RECEIVED: case PAYOUT_PUBLISHED: - persistPendingTrades(); + // persistPendingTrades(); break; case TAKE_OFFER_FEE_PUBLISH_FAILED: case MESSAGE_SENDING_FAILED: @@ -541,16 +522,4 @@ public class TradeManager { }); } - private void persistOpenOfferTrades() { - persistence.write(this, "openOfferTrades", (List) new ArrayList<>(openOfferTrades)); - } - - private void persistPendingTrades() { - persistence.write(this, "pendingTrades", (List) new ArrayList<>(pendingTrades)); - } - - private void persistClosedTrades() { - persistence.write(this, "closedTrades", (List) new ArrayList<>(closedTrades)); - } - } \ No newline at end of file diff --git a/core/src/main/java/io/bitsquare/trade/TradesList.java b/core/src/main/java/io/bitsquare/trade/TradesList.java new file mode 100644 index 0000000000..1b92fba667 --- /dev/null +++ b/core/src/main/java/io/bitsquare/trade/TradesList.java @@ -0,0 +1,72 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.trade; + +import io.bitsquare.storage.Storage; + +import com.google.inject.Inject; + +import java.io.Serializable; + +import java.util.ArrayList; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TradesList extends ArrayList implements Serializable { + private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(TradesList.class); + + transient final private Storage storage; + transient private ObservableList observableList; + + @Inject + public TradesList(Storage storage) { + this.storage = storage; + + TradesList persisted = storage.initAndGetPersisted(this); + if (persisted != null) { + addAll(persisted); + observableList = FXCollections.observableArrayList(this); + } + else { + observableList = FXCollections.observableArrayList(this); + } + } + + @Override + public boolean add(Trade trade) { + boolean result = super.add(trade); + storage.save(); + return result; + } + + @Override + public boolean remove(Object trade) { + boolean result = super.remove(trade); + storage.save(); + return result; + } + + public ObservableList getObservableList() { + return observableList; + } +} diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/SharedTradeModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/SharedTradeModel.java index 5f7d54eb96..69a38e24f1 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/SharedTradeModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/SharedTradeModel.java @@ -26,7 +26,6 @@ import io.bitsquare.offer.Offer; import io.bitsquare.p2p.MailboxMessage; import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MessageService; -import io.bitsquare.persistence.Persistence; import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import java.io.Serializable; @@ -45,7 +44,6 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable { transient public final WalletService walletService; transient public final BlockChainService blockChainService; transient public final SignatureService signatureService; - transient protected final Persistence persistence; transient public MailboxMessage mailboxMessage; @@ -62,15 +60,13 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable { MailboxService mailboxService, WalletService walletService, BlockChainService blockChainService, - SignatureService signatureService, - Persistence persistence) { + SignatureService signatureService) { this.offer = offer; this.messageService = messageService; this.mailboxService = mailboxService; this.walletService = walletService; this.blockChainService = blockChainService; this.signatureService = signatureService; - this.persistence = persistence; id = offer.getId(); tradeWalletService = walletService.getTradeWalletService(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/models/OffererAsBuyerModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/models/OffererAsBuyerModel.java index 01ddad375b..9a95012d5f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/models/OffererAsBuyerModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/models/OffererAsBuyerModel.java @@ -22,11 +22,12 @@ import io.bitsquare.btc.WalletService; import io.bitsquare.crypto.SignatureService; import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MessageService; -import io.bitsquare.persistence.Persistence; +import io.bitsquare.storage.Storage; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.SharedTradeModel; import io.bitsquare.user.User; +import java.io.File; import java.io.Serializable; import org.slf4j.Logger; @@ -34,9 +35,11 @@ import org.slf4j.LoggerFactory; public class OffererAsBuyerModel extends SharedTradeModel implements Serializable { private static final long serialVersionUID = 5000457153390911569L; - private static final Logger log = LoggerFactory.getLogger(OffererAsBuyerModel.class); + transient private static final Logger log = LoggerFactory.getLogger(OffererAsBuyerModel.class); + transient private Storage storage; transient public final Trade trade; + public final Taker taker; public final Offerer offerer; @@ -50,26 +53,25 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl BlockChainService blockChainService, SignatureService signatureService, User user, - Persistence persistence) { + File storageDir) { super(trade.getOffer(), messageService, mailboxService, walletService, blockChainService, - signatureService, - persistence); + signatureService); this.trade = trade; - - Serializable serializable = persistence.read(this, "BuyerAsOffererModel_" + id); - if (serializable instanceof OffererAsBuyerModel) { - OffererAsBuyerModel persistedModel = (OffererAsBuyerModel) serializable; + this.storage = new Storage<>(storageDir); + + OffererAsBuyerModel persisted = storage.initAndGetPersisted(this, getClass().getSimpleName() + id); + if (persisted != null) { log.debug("Model reconstructed form persisted model."); - setTakeOfferFeeTxId(persistedModel.takeOfferFeeTxId); + setTakeOfferFeeTxId(persisted.takeOfferFeeTxId); - taker = persistedModel.taker; - offerer = persistedModel.offerer; + taker = persisted.taker; + offerer = persisted.offerer; } else { taker = new Taker(); @@ -90,13 +92,13 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl // Get called form taskRunner after each completed task @Override public void persist() { - persistence.write(this, "BuyerAsOffererModel_" + id, this); + storage.save(); } @Override public void onComplete() { // Just in case of successful completion we delete our persisted object - persistence.remove(this, "BuyerAsOffererModel_" + id); + storage.remove(); } public String getTakeOfferFeeTxId() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/models/TakerAsSellerModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/models/TakerAsSellerModel.java index 4296232869..68133ac1bd 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/models/TakerAsSellerModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/models/TakerAsSellerModel.java @@ -22,13 +22,14 @@ import io.bitsquare.btc.WalletService; import io.bitsquare.crypto.SignatureService; import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MessageService; -import io.bitsquare.persistence.Persistence; +import io.bitsquare.storage.Storage; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.SharedTradeModel; import io.bitsquare.user.User; import org.bitcoinj.core.Transaction; +import java.io.File; import java.io.Serializable; import org.slf4j.Logger; @@ -36,9 +37,11 @@ import org.slf4j.LoggerFactory; public class TakerAsSellerModel extends SharedTradeModel implements Serializable { private static final long serialVersionUID = -963501132927618376L; - private static final Logger log = LoggerFactory.getLogger(TakerAsSellerModel.class); + transient private static final Logger log = LoggerFactory.getLogger(TakerAsSellerModel.class); + + transient private Storage storage; + transient public final Trade trade; - public final Trade trade; public final Taker taker; public final Offerer offerer; @@ -52,28 +55,27 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable WalletService walletService, BlockChainService blockChainService, SignatureService signatureService, - User user, - Persistence persistence) { + User user, + File storageDir) { super(trade.getOffer(), messageService, mailboxService, walletService, blockChainService, - signatureService, - persistence); + signatureService); this.trade = trade; + this.storage = new Storage<>(storageDir); - Serializable serializable = persistence.read(this, "SellerAsTakerModel_" + id); - if (serializable instanceof TakerAsSellerModel) { - TakerAsSellerModel persistedModel = (TakerAsSellerModel) serializable; - log.debug("Model reconstructed form persisted model."); + TakerAsSellerModel persisted = storage.initAndGetPersisted(this, getClass().getSimpleName() + id); + if (persisted != null) { + log.debug("Model reconstructed from persisted model."); - setTakeOfferFeeTx(persistedModel.getTakeOfferFeeTx()); - setPayoutTx(persistedModel.payoutTx); + setTakeOfferFeeTx(persisted.getTakeOfferFeeTx()); + setPayoutTx(persisted.payoutTx); - taker = persistedModel.taker; - offerer = persistedModel.offerer; + taker = persisted.taker; + offerer = persisted.offerer; } else { taker = new Taker(); @@ -93,13 +95,13 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable // Get called form taskRunner after each completed task @Override public void persist() { - persistence.write(this, "SellerAsTakerModel_" + id, this); + storage.save(); } @Override public void onComplete() { // Just in case of successful completion we delete our persisted object - persistence.remove(this, "SellerAsTakerModel_" + id); + storage.remove(); } diff --git a/core/src/main/java/io/bitsquare/user/AccountSettings.java b/core/src/main/java/io/bitsquare/user/AccountSettings.java index c0e8cdcaad..38c916bd56 100644 --- a/core/src/main/java/io/bitsquare/user/AccountSettings.java +++ b/core/src/main/java/io/bitsquare/user/AccountSettings.java @@ -21,7 +21,7 @@ import io.bitsquare.arbitration.Arbitrator; import io.bitsquare.locale.Country; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.LanguageUtil; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import org.bitcoinj.core.Coin; @@ -44,6 +44,7 @@ public class AccountSettings implements Serializable { private List acceptedLanguageLocales = new ArrayList<>(); private List acceptedCountryLocales = new ArrayList<>(); private List acceptedArbitrators = new ArrayList<>(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -53,7 +54,7 @@ public class AccountSettings implements Serializable { public AccountSettings(Storage storage, Arbitrator defaultArbitrator) { this.storage = storage; - AccountSettings persisted = storage.getPersisted(this); + AccountSettings persisted = storage.initAndGetPersisted(this); if (persisted != null) { acceptedLanguageLocales = persisted.getAcceptedLanguageLocales(); acceptedCountryLocales = persisted.getAcceptedCountries(); @@ -106,7 +107,7 @@ public class AccountSettings implements Serializable { storage.save(); } - + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/io/bitsquare/user/Preferences.java b/core/src/main/java/io/bitsquare/user/Preferences.java index bc589c91cf..e701a36e09 100644 --- a/core/src/main/java/io/bitsquare/user/Preferences.java +++ b/core/src/main/java/io/bitsquare/user/Preferences.java @@ -17,7 +17,7 @@ package io.bitsquare.user; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import org.bitcoinj.utils.MonetaryFormat; @@ -53,13 +53,13 @@ public class Preferences implements Serializable { private String _btcDenomination = MonetaryFormat.CODE_BTC; private Boolean _useAnimations = true; private Boolean _useEffects = true; + private Boolean displaySecurityDepositInfo = true; // Observable wrappers transient private final StringProperty btcDenomination = new SimpleStringProperty(_btcDenomination); transient private final BooleanProperty useAnimations = new SimpleBooleanProperty(_useAnimations); transient private final BooleanProperty useEffects = new SimpleBooleanProperty(_useEffects); - /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -68,11 +68,12 @@ public class Preferences implements Serializable { public Preferences(Storage storage) { this.storage = storage; - Preferences persisted = storage.getPersisted(this); + Preferences persisted = storage.initAndGetPersisted(this); if (persisted != null) { setBtcDenomination(persisted._btcDenomination); setUseAnimations(persisted._useAnimations); setUseEffects(persisted._useEffects); + displaySecurityDepositInfo = persisted.getDisplaySecurityDepositInfo(); } // Use that to guarantee update of the serializable field and to make a storage update in case of a change @@ -107,6 +108,11 @@ public class Preferences implements Serializable { this.useEffects.set(useEffects); } + public void setDisplaySecurityDepositInfo(Boolean displaySecurityDepositInfo) { + this.displaySecurityDepositInfo = displaySecurityDepositInfo; + storage.save(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -124,6 +130,10 @@ public class Preferences implements Serializable { return useAnimations.get(); } + public Boolean getDisplaySecurityDepositInfo() { + return displaySecurityDepositInfo; + } + public StringProperty btcDenominationProperty() { return btcDenomination; } @@ -135,4 +145,6 @@ public class Preferences implements Serializable { public BooleanProperty useEffectsProperty() { return useEffects; } + + } diff --git a/core/src/main/java/io/bitsquare/user/User.java b/core/src/main/java/io/bitsquare/user/User.java index 9d96e9c10c..765d8b7d30 100644 --- a/core/src/main/java/io/bitsquare/user/User.java +++ b/core/src/main/java/io/bitsquare/user/User.java @@ -20,7 +20,7 @@ package io.bitsquare.user; import io.bitsquare.crypto.EncryptionService; import io.bitsquare.fiat.FiatAccount; import io.bitsquare.gui.components.Popups; -import io.bitsquare.persistence.Storage; +import io.bitsquare.storage.Storage; import java.io.Serializable; @@ -72,7 +72,7 @@ public class User implements Serializable { this.storage = storage; this.encryptionService = encryptionService; - User persisted = storage.getPersisted(this); + User persisted = storage.initAndGetPersisted(this); if (persisted != null) { p2pSigKeyPair = persisted.getP2pSigKeyPair(); p2pEncryptKeyPair = persisted.getP2pEncryptKeyPair(); diff --git a/core/src/main/java/io/bitsquare/util/FileUtil.java b/core/src/main/java/io/bitsquare/util/FileUtil.java deleted file mode 100644 index 39a770fbf4..0000000000 --- a/core/src/main/java/io/bitsquare/util/FileUtil.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * This file is part of Bitsquare. - * - * Bitsquare is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bitsquare is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bitsquare. If not, see . - */ - -package io.bitsquare.util; - -import org.bitcoinj.core.Utils; -import org.bitcoinj.utils.Threading; - -import com.google.common.io.Files; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; - -import java.util.concurrent.locks.ReentrantLock; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FileUtil { - private static final Logger log = LoggerFactory.getLogger(FileUtil.class); - private static final ReentrantLock lock = Threading.lock("FileUtil"); - - public static void write(Serializable serializable, File dir, File storageFile) throws IOException { - lock.lock(); - File tempFile = null; - FileOutputStream fileOutputStream = null; - ObjectOutputStream objectOutputStream = null; - try { - if (!dir.exists()) - dir.mkdir(); - - tempFile = File.createTempFile("temp", null, dir); - - // Don't use auto closeable resources in try() as we would need too many try/catch clauses (for tempFile) - // and we need to close it - // manually before replacing file with temp file - fileOutputStream = new FileOutputStream(tempFile); - objectOutputStream = new ObjectOutputStream(fileOutputStream); - - objectOutputStream.writeObject(serializable); - - // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide - // to not write through to physical media for at least a few seconds, but this is the best we can do. - fileOutputStream.flush(); - fileOutputStream.getFD().sync(); - - // Close resources before replacing file with temp file because otherwise it causes problems on windows - // when rename temp file - fileOutputStream.close(); - objectOutputStream.close(); - - writeTempFileToFile(tempFile, storageFile); - } finally { - if (tempFile != null && tempFile.exists()) { - log.warn("Temp file still exists after failed save."); - if (!tempFile.delete()) log.error("Cannot delete temp file."); - } - - try { - if (objectOutputStream != null) - objectOutputStream.close(); - if (fileOutputStream != null) - fileOutputStream.close(); - } catch (IOException e) { - // We swallow that - e.printStackTrace(); - log.error("Cannot close resources."); - } - lock.unlock(); - } - } - - public static Object read(File file) throws IOException, ClassNotFoundException { - lock.lock(); - try (final FileInputStream fileInputStream = new FileInputStream(file); - final ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) { - return objectInputStream.readObject(); - } finally { - lock.unlock(); - } - } - - private static void writeTempFileToFile(File tempFile, File file) throws IOException { - lock.lock(); - try { - if (Utils.isWindows()) { - // Work around an issue on Windows whereby you can't rename over existing files. - final File canonical = file.getCanonicalFile(); - if (canonical.exists() && !canonical.delete()) { - throw new IOException("Failed to delete canonical file for replacement with save"); - } - if (!tempFile.renameTo(canonical)) { - throw new IOException("Failed to rename " + tempFile + " to " + canonical); - } - } - else if (!tempFile.renameTo(file)) { - throw new IOException("Failed to rename " + tempFile + " to " + file); - } - } finally { - lock.unlock(); - } - } - - public static void removeAndBackupFile(File storageFile, File dir, String name) throws IOException { - if (!dir.exists()) - dir.mkdir(); - - writeTempFileToFile(storageFile, new File(dir, name)); - } - - public static void backupFile(File storageFile, File dir, String name) throws IOException { - if (!dir.exists()) - dir.mkdir(); - - Files.copy(storageFile, new File(dir, name)); - } -} diff --git a/core/src/test/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModelTest.java b/core/src/test/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModelTest.java index 9d118b5f44..8ce6b447e6 100644 --- a/core/src/test/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModelTest.java +++ b/core/src/test/java/io/bitsquare/gui/main/trade/createoffer/CreateOfferViewModelTest.java @@ -44,7 +44,7 @@ public class CreateOfferViewModelTest { BSFormatter formatter = new BSFormatter(new User()); formatter.setLocale(Locale.US); formatter.setFiatCurrencyCode("USD"); - model = new CreateOfferDataModel(null, null, null, null, null, null, formatter); + model = new CreateOfferDataModel(null, null, null, null, null, formatter); presenter = new CreateOfferViewModel(model, new FiatValidator(null), new BtcValidator(), formatter); } diff --git a/core/src/test/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocolTest.java b/core/src/test/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocolTest.java index d680f5f008..eca75daaa1 100644 --- a/core/src/test/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocolTest.java +++ b/core/src/test/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferProtocolTest.java @@ -17,38 +17,21 @@ package io.bitsquare.trade.protocol.placeoffer; -import io.bitsquare.btc.BitcoinNetwork; -import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.UserAgent; import io.bitsquare.btc.WalletService; import io.bitsquare.offer.OfferBookService; -import io.bitsquare.offer.tomp2p.TomP2POfferBookService; -import io.bitsquare.p2p.BootstrapState; import io.bitsquare.p2p.MessageService; -import io.bitsquare.p2p.Node; import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder; -import io.bitsquare.p2p.tomp2p.TomP2PMessageService; import io.bitsquare.p2p.tomp2p.TomP2PNode; -import io.bitsquare.persistence.Persistence; -import io.bitsquare.user.User; import org.bitcoinj.core.Address; -import org.bitcoinj.utils.Threading; import java.io.File; -import java.io.IOException; -import java.util.concurrent.CountDownLatch; - -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import rx.Observable; - /** * That test is ignored for automated testing as it needs custom setup. *

@@ -72,22 +55,19 @@ public class PlaceOfferProtocolTest { private TomP2PNode tomP2PNode; private BootstrappedPeerBuilder bootstrappedPeerBuilder; - @Before + /* @Before public void setup() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); dir.mkdirs(); - Persistence persistence = new Persistence(dir, "prefs"); - persistence.init(); - // messageService Node bootstrapNode = Node.at("localhost", "127.0.0.1"); User user = new User(); - /* try { + *//* try { user.initPersistedObject(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); - }*/ + }*//* bootstrappedPeerBuilder = new BootstrappedPeerBuilder(Node.DEFAULT_PORT, false, bootstrapNode, ""); tomP2PNode = new TomP2PNode(bootstrappedPeerBuilder); messageService = new TomP2PMessageService(tomP2PNode, null, null, null); @@ -153,7 +133,7 @@ public class PlaceOfferProtocolTest { public void shutDown() throws IOException, InterruptedException { walletService.shutDown(); bootstrappedPeerBuilder.shutDown(); - } + }*/ /* @Test public void validateOfferTest() throws InterruptedException {