Use delayed write on background thread for persistence

This commit is contained in:
Manfred Karrer 2015-03-24 21:10:35 +01:00
parent b807ee17a6
commit b96b133c31
35 changed files with 635 additions and 777 deletions

View file

@ -26,7 +26,6 @@ import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.debug.DebugView; import io.bitsquare.gui.main.debug.DebugView;
import io.bitsquare.gui.util.ImageUtil; import io.bitsquare.gui.util.ImageUtil;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
@ -74,20 +73,13 @@ public class BitsquareApp extends Application {
bitsquareAppModule = new BitsquareAppModule(env, primaryStage); bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule); injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector); injector.getInstance(InjectorViewFactory.class).setInjector(injector);
// route uncaught exceptions to a user-facing dialog // route uncaught exceptions to a user-facing dialog
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) ->
Popups.handleUncaughtExceptions(Throwables.getRootCause(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 // load the main view and create the main scene
log.trace("viewLoader.load(MainView.class)"); log.trace("viewLoader.load(MainView.class)");
CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class); CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class);
View view = viewLoader.load(MainView.class); View view = viewLoader.load(MainView.class);

View file

@ -23,12 +23,11 @@ import io.bitsquare.arbitration.tomp2p.TomP2PArbitratorModule;
import io.bitsquare.btc.BitcoinModule; import io.bitsquare.btc.BitcoinModule;
import io.bitsquare.crypto.CryptoModule; import io.bitsquare.crypto.CryptoModule;
import io.bitsquare.gui.GuiModule; 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.OfferModule;
import io.bitsquare.offer.tomp2p.TomP2POfferModule; import io.bitsquare.offer.tomp2p.TomP2POfferModule;
import io.bitsquare.persistence.Persistence; import io.bitsquare.p2p.P2PModule;
import io.bitsquare.persistence.Storage; import io.bitsquare.p2p.tomp2p.TomP2PModule;
import io.bitsquare.storage.Storage;
import io.bitsquare.trade.TradeModule; import io.bitsquare.trade.TradeModule;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
@ -63,14 +62,9 @@ class BitsquareAppModule extends BitsquareModule {
File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY)); File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY));
bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir); 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(Environment.class).toInstance(env);
bind(UpdateProcess.class).in(Singleton.class); bind(UpdateProcess.class).in(Singleton.class);
install(networkModule()); install(networkModule());
install(bitcoinModule()); install(bitcoinModule());
install(cryptoModule()); install(cryptoModule());

View file

@ -21,8 +21,7 @@ import io.bitsquare.BitsquareException;
import io.bitsquare.btc.UserAgent; import io.bitsquare.btc.UserAgent;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.persistence.Persistence; import io.bitsquare.storage.Storage;
import io.bitsquare.persistence.Storage;
import io.bitsquare.util.Utilities; import io.bitsquare.util.Utilities;
import io.bitsquare.util.spring.JOptCommandLinePropertySource; 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(Storage.DIR_KEY, Paths.get(appDataDir, "db").toString());
setProperty(Persistence.DIR_KEY, appDataDir);
setProperty(Persistence.PREFIX_KEY, appName + "_pref");
setProperty(MainView.TITLE_KEY, appName); setProperty(MainView.TITLE_KEY, appName);
}}); }});
} }

View file

@ -18,7 +18,7 @@
package io.bitsquare.arbitration; package io.bitsquare.arbitration;
import io.bitsquare.locale.LanguageUtil; import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -95,7 +95,7 @@ public class Arbitrator implements Serializable {
public Arbitrator(Storage<Arbitrator> storage, User user) { public Arbitrator(Storage<Arbitrator> storage, User user) {
this.storage = storage; this.storage = storage;
Arbitrator persisted = storage.getPersisted(this); Arbitrator persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
//TODO for mock arbitrator //TODO for mock arbitrator
id = persisted.getName(); id = persisted.getName();

View file

@ -17,7 +17,7 @@
package io.bitsquare.btc; package io.bitsquare.btc;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import org.bitcoinj.core.Wallet; import org.bitcoinj.core.Wallet;
import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.DeterministicKey;
@ -48,7 +48,7 @@ public class AddressEntryList extends ArrayList<AddressEntry> implements Seriali
public void init(Wallet wallet) { public void init(Wallet wallet) {
this.wallet = wallet; this.wallet = wallet;
AddressEntryList persisted = storage.getPersisted(this); AddressEntryList persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
for (AddressEntry addressEntry : persisted) { for (AddressEntry addressEntry : persisted) {
addressEntry.setDeterministicKey((DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash())); addressEntry.setDeterministicKey((DeterministicKey) wallet.findKeyFromPubHash(addressEntry.getPubKeyHash()));

View file

@ -21,7 +21,6 @@ import io.bitsquare.btc.listeners.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener; import io.bitsquare.btc.listeners.TxConfidenceListener;
import io.bitsquare.crypto.SignatureService; import io.bitsquare.crypto.SignatureService;
import io.bitsquare.persistence.Persistence;
import org.bitcoinj.core.AbstractWalletEventListener; import org.bitcoinj.core.AbstractWalletEventListener;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
@ -91,11 +90,9 @@ public class WalletService {
private final NetworkParameters params; private final NetworkParameters params;
private final FeePolicy feePolicy; private final FeePolicy feePolicy;
private final SignatureService signatureService; private final SignatureService signatureService;
private final Persistence persistence;
private final File walletDir; private final File walletDir;
private final String walletPrefix; private final String walletPrefix;
private final UserAgent userAgent; private final UserAgent userAgent;
private final BitcoinNetwork bitcoinNetwork;
private WalletAppKit walletAppKit; private WalletAppKit walletAppKit;
private Wallet wallet; private Wallet wallet;
@ -111,14 +108,12 @@ public class WalletService {
@Inject @Inject
public WalletService(BitcoinNetwork bitcoinNetwork, FeePolicy feePolicy, SignatureService signatureService, 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) { @Named(DIR_KEY) File walletDir, @Named(PREFIX_KEY) String walletPrefix) {
this.bitcoinNetwork = bitcoinNetwork;
this.addressEntryList = addressEntryList; this.addressEntryList = addressEntryList;
this.params = bitcoinNetwork.getParameters(); this.params = bitcoinNetwork.getParameters();
this.feePolicy = feePolicy; this.feePolicy = feePolicy;
this.signatureService = signatureService; this.signatureService = signatureService;
this.persistence = persistence;
this.walletDir = walletDir; this.walletDir = walletDir;
this.walletPrefix = walletPrefix; this.walletPrefix = walletPrefix;
this.userAgent = userAgent; this.userAgent = userAgent;

View file

@ -21,7 +21,7 @@ import io.bitsquare.common.viewfx.view.View;
import io.bitsquare.common.viewfx.view.ViewPath; import io.bitsquare.common.viewfx.view.ViewPath;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.trade.BuyView; import io.bitsquare.gui.main.trade.BuyView;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -62,7 +62,7 @@ public class Navigation implements Serializable {
public Navigation(Storage<Navigation> storage) { public Navigation(Storage<Navigation> storage) {
this.storage = storage; this.storage = storage;
Navigation persisted = storage.getPersisted(this); Navigation persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
previousPath = persisted.getPreviousPath(); previousPath = persisted.getPreviousPath();
} }

View file

@ -68,9 +68,8 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
private final String title; private final String title;
@Inject @Inject
public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager public MainView(MainViewModel model, CachingViewLoader viewLoader, Navigation navigation, OverlayManager overlayManager, Transitions transitions,
overlayManager, @Named(MainView.TITLE_KEY) String title) {
Transitions transitions, @Named(MainView.TITLE_KEY) String title) {
super(model); super(model);
this.viewLoader = viewLoader; this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;

View file

@ -18,7 +18,6 @@
package io.bitsquare.gui.main; package io.bitsquare.gui.main;
import io.bitsquare.app.UpdateProcess; import io.bitsquare.app.UpdateProcess;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.common.viewfx.model.ViewModel; 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.BaseP2PService;
import io.bitsquare.p2p.BootstrapState; import io.bitsquare.p2p.BootstrapState;
import io.bitsquare.p2p.ClientNode; import io.bitsquare.p2p.ClientNode;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -91,27 +88,20 @@ class MainViewModel implements ViewModel {
private final User user; private final User user;
private final WalletService walletService; private final WalletService walletService;
private final ClientNode clientNode; private final ClientNode clientNode;
private ArbitratorService arbitratorService;
private final TradeManager tradeManager; private final TradeManager tradeManager;
private UpdateProcess updateProcess; private UpdateProcess updateProcess;
private final BSFormatter formatter; private final BSFormatter formatter;
private Persistence persistence;
private AccountSettings accountSettings;
@Inject @Inject
public MainViewModel(User user, WalletService walletService, ClientNode clientNode, public MainViewModel(User user, WalletService walletService, ClientNode clientNode,
ArbitratorService arbitratorService,
TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess, TradeManager tradeManager, BitcoinNetwork bitcoinNetwork, UpdateProcess updateProcess,
BSFormatter formatter, Persistence persistence, AccountSettings accountSettings) { BSFormatter formatter) {
this.user = user; this.user = user;
this.walletService = walletService; this.walletService = walletService;
this.clientNode = clientNode; this.clientNode = clientNode;
this.arbitratorService = arbitratorService;
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.updateProcess = updateProcess; this.updateProcess = updateProcess;
this.formatter = formatter; this.formatter = formatter;
this.persistence = persistence;
this.accountSettings = accountSettings;
bitcoinNetworkAsString = bitcoinNetwork.toString(); bitcoinNetworkAsString = bitcoinNetwork.toString();

View file

@ -27,7 +27,6 @@ import io.bitsquare.common.viewfx.view.View;
import io.bitsquare.common.viewfx.view.ViewLoader; import io.bitsquare.common.viewfx.view.ViewLoader;
import io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView; import io.bitsquare.gui.main.account.arbitrator.profile.ArbitratorProfileView;
import io.bitsquare.locale.LanguageUtil; import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import java.util.ArrayList; import java.util.ArrayList;
@ -54,15 +53,13 @@ public class ArbitratorBrowserView extends ActivatableView<Pane, Void> implement
private final ViewLoader viewLoader; private final ViewLoader viewLoader;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private final Persistence persistence;
private final ArbitratorService messageService; private final ArbitratorService messageService;
@Inject @Inject
public ArbitratorBrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings, Persistence persistence, public ArbitratorBrowserView(CachingViewLoader viewLoader, AccountSettings accountSettings,
ArbitratorService messageService) { ArbitratorService messageService) {
this.viewLoader = viewLoader; this.viewLoader = viewLoader;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
this.persistence = persistence;
this.messageService = messageService; this.messageService = messageService;
} }

View file

@ -25,7 +25,6 @@ import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.locale.Region; import io.bitsquare.locale.Region;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User; import io.bitsquare.user.User;
@ -46,7 +45,6 @@ class FiatAccountDataModel implements Activatable, DataModel {
private final User user; private final User user;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private final Persistence persistence;
final StringProperty title = new SimpleStringProperty(); final StringProperty title = new SimpleStringProperty();
final StringProperty holderName = new SimpleStringProperty(); final StringProperty holderName = new SimpleStringProperty();
@ -68,8 +66,7 @@ class FiatAccountDataModel implements Activatable, DataModel {
@Inject @Inject
public FiatAccountDataModel(User user, Persistence persistence, AccountSettings accountSettings) { public FiatAccountDataModel(User user, AccountSettings accountSettings) {
this.persistence = persistence;
this.user = user; this.user = user;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
} }

View file

@ -17,15 +17,12 @@
package io.bitsquare.gui.main.account.content.irc; 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.Activatable;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.fiat.FiatAccount; import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.fiat.FiatAccountType; import io.bitsquare.fiat.FiatAccountType;
import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -42,9 +39,6 @@ import javafx.collections.ObservableList;
class IrcAccountDataModel implements Activatable, DataModel { class IrcAccountDataModel implements Activatable, DataModel {
private final User user; private final User user;
private final AccountSettings accountSettings;
private final ArbitratorService messageService;
private final Persistence persistence;
final StringProperty nickName = new SimpleStringProperty(); final StringProperty nickName = new SimpleStringProperty();
final ObjectProperty<FiatAccountType> type = new SimpleObjectProperty<>(); final ObjectProperty<FiatAccountType> type = new SimpleObjectProperty<>();
@ -57,12 +51,8 @@ class IrcAccountDataModel implements Activatable, DataModel {
@Inject @Inject
public IrcAccountDataModel(User user, Persistence persistence, AccountSettings accountSettings, public IrcAccountDataModel(User user) {
ArbitratorService messageService) {
this.persistence = persistence;
this.user = user; this.user = user;
this.accountSettings = accountSettings;
this.messageService = messageService;
} }
@Override @Override

View file

@ -22,7 +22,6 @@ import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -50,7 +49,6 @@ class RegistrationDataModel implements DataModel {
private final WalletService walletService; private final WalletService walletService;
private final User user; private final User user;
private final Persistence persistence;
private String transactionId; private String transactionId;
private AddressEntry addressEntry; private AddressEntry addressEntry;
@ -61,11 +59,10 @@ class RegistrationDataModel implements DataModel {
@Inject @Inject
public RegistrationDataModel(WalletService walletService, User user, Persistence persistence) { public RegistrationDataModel(WalletService walletService, User user) {
this.walletService = walletService; this.walletService = walletService;
this.user = user; this.user = user;
this.persistence = persistence;
if (walletService != null && walletService.getWallet() != null) { if (walletService != null && walletService.getWallet() != null) {
addressEntry = walletService.getRegistrationAddressEntry(); addressEntry = walletService.getRegistrationAddressEntry();

View file

@ -18,16 +18,13 @@
package io.bitsquare.gui.main.account.content.restrictions; package io.bitsquare.gui.main.account.content.restrictions;
import io.bitsquare.arbitration.Arbitrator; import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.arbitration.ArbitratorService;
import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.locale.Country; import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.LanguageUtil; import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.Region; import io.bitsquare.locale.Region;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.User;
import com.google.inject.Inject; import com.google.inject.Inject;
@ -38,10 +35,7 @@ import javafx.collections.ObservableList;
class RestrictionsDataModel implements Activatable, DataModel { class RestrictionsDataModel implements Activatable, DataModel {
private final User user;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private final Persistence persistence;
private final ArbitratorService messageService;
final ObservableList<Locale> languageList = FXCollections.observableArrayList(); final ObservableList<Locale> languageList = FXCollections.observableArrayList();
final ObservableList<Country> countryList = FXCollections.observableArrayList(); final ObservableList<Country> countryList = FXCollections.observableArrayList();
@ -52,12 +46,8 @@ class RestrictionsDataModel implements Activatable, DataModel {
@Inject @Inject
public RestrictionsDataModel(User user, AccountSettings accountSettings, Persistence persistence, public RestrictionsDataModel(AccountSettings accountSettings) {
ArbitratorService messageService) {
this.user = user;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
this.persistence = persistence;
this.messageService = messageService;
} }
@Override @Override

View file

@ -28,7 +28,6 @@ import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.Country; import io.bitsquare.locale.Country;
import io.bitsquare.offer.Direction; import io.bitsquare.offer.Direction;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.AccountSettings; import io.bitsquare.user.AccountSettings;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
@ -72,8 +71,6 @@ class CreateOfferDataModel implements Activatable, DataModel {
private final WalletService walletService; private final WalletService walletService;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private Preferences preferences; private Preferences preferences;
private final User user;
private final Persistence persistence;
private final BSFormatter formatter; private final BSFormatter formatter;
private final String offerId; private final String offerId;
@ -110,14 +107,12 @@ class CreateOfferDataModel implements Activatable, DataModel {
// non private for testing // non private for testing
@Inject @Inject
public CreateOfferDataModel(TradeManager tradeManager, WalletService walletService, AccountSettings accountSettings, public CreateOfferDataModel(TradeManager tradeManager, WalletService walletService, AccountSettings accountSettings,
Preferences preferences, User user, Persistence persistence, Preferences preferences, User user,
BSFormatter formatter) { BSFormatter formatter) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.walletService = walletService; this.walletService = walletService;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
this.preferences = preferences; this.preferences = preferences;
this.user = user;
this.persistence = persistence;
this.formatter = formatter; this.formatter = formatter;
this.offerId = UUID.randomUUID().toString(); this.offerId = UUID.randomUUID().toString();
@ -231,7 +226,7 @@ class CreateOfferDataModel implements Activatable, DataModel {
} }
void securityDepositInfoDisplayed() { void securityDepositInfoDisplayed() {
persistence.write("displaySecurityDepositInfo", false); preferences.setDisplaySecurityDepositInfo(false);
} }
@ -255,15 +250,6 @@ class CreateOfferDataModel implements Activatable, DataModel {
return offerId; 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) { private void updateBalance(@NotNull Coin balance) {
isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0);
} }
@ -281,4 +267,8 @@ class CreateOfferDataModel implements Activatable, DataModel {
fiatCode.set(fiatAccount.getCurrency().getCurrencyCode()); fiatCode.set(fiatAccount.getCurrency().getCurrencyCode());
} }
} }
public Boolean getDisplaySecurityDepositInfo() {
return preferences.getDisplaySecurityDepositInfo();
}
} }

View file

@ -147,7 +147,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
@FXML @FXML
void onShowPayFundsScreen() { void onShowPayFundsScreen() {
// TODO deactivate for testing the moment // TODO deactivate for testing the moment
/* if (model.displaySecurityDepositInfo()) { /* if (model.getDisplaySecurityDepositInfo()) {
overlayManager.blurContent(); overlayManager.blurContent();
List<Action> actions = new ArrayList<>(); List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) { actions.add(new AbstractAction(BSResources.get("shared.close")) {

View file

@ -250,8 +250,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
return formatter; return formatter;
} }
Boolean displaySecurityDepositInfo() { Boolean getDisplaySecurityDepositInfo() {
return dataModel.displaySecurityDepositInfo(); return dataModel.getDisplaySecurityDepositInfo();
} }
private void setupListeners() { private void setupListeners() {

View file

@ -24,7 +24,6 @@ import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.common.viewfx.model.Activatable; import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel; import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.offer.Offer; import io.bitsquare.offer.Offer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.handlers.TradeResultHandler; import io.bitsquare.trade.handlers.TradeResultHandler;
import io.bitsquare.user.Preferences; import io.bitsquare.user.Preferences;
@ -58,7 +57,6 @@ class TakeOfferDataModel implements Activatable, DataModel {
private final TradeManager tradeManager; private final TradeManager tradeManager;
private final WalletService walletService; private final WalletService walletService;
private final Preferences preferences; private final Preferences preferences;
private final Persistence persistence;
private Offer offer; private Offer offer;
private AddressEntry addressEntry; private AddressEntry addressEntry;
@ -78,12 +76,10 @@ class TakeOfferDataModel implements Activatable, DataModel {
@Inject @Inject
public TakeOfferDataModel(TradeManager tradeManager, public TakeOfferDataModel(TradeManager tradeManager,
WalletService walletService, WalletService walletService,
Preferences preferences, Preferences preferences) {
Persistence persistence) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.walletService = walletService; this.walletService = walletService;
this.preferences = preferences; this.preferences = preferences;
this.persistence = persistence;
offerFeeAsCoin.set(FeePolicy.CREATE_OFFER_FEE); offerFeeAsCoin.set(FeePolicy.CREATE_OFFER_FEE);
networkFeeAsCoin.set(FeePolicy.TX_FEE); networkFeeAsCoin.set(FeePolicy.TX_FEE);
@ -174,16 +170,12 @@ class TakeOfferDataModel implements Activatable, DataModel {
return true; return true;
} }
Boolean displaySecurityDepositInfo() { Boolean getDisplaySecurityDepositInfo() {
Object securityDepositInfoDisplayedObject = persistence.read("displaySecurityDepositInfo"); return preferences.getDisplaySecurityDepositInfo();
if (securityDepositInfoDisplayedObject instanceof Boolean)
return (Boolean) securityDepositInfoDisplayedObject;
else
return true;
} }
void securityDepositInfoDisplayed() { void securityDepositInfoDisplayed() {
persistence.write("displaySecurityDepositInfo", false); preferences.setDisplaySecurityDepositInfo(false);
} }

View file

@ -319,7 +319,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void setupPaymentScreen() { private void setupPaymentScreen() {
// TODO deactivate for testing the moment // TODO deactivate for testing the moment
/* if (model.displaySecurityDepositInfo()) { /* if (model.getDisplaySecurityDepositInfo()) {
overlayManager.blurContent(); overlayManager.blurContent();
List<Action> actions = new ArrayList<>(); List<Action> actions = new ArrayList<>();
actions.add(new AbstractAction(BSResources.get("shared.close")) { actions.add(new AbstractAction(BSResources.get("shared.close")) {

View file

@ -359,8 +359,8 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return paymentLabel; return paymentLabel;
} }
Boolean displaySecurityDepositInfo() { Boolean getDisplaySecurityDepositInfo() {
return dataModel.displaySecurityDepositInfo(); return dataModel.getDisplaySecurityDepositInfo();
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, Serializable> rootMap = new HashMap<>();
private Map<String, Long> 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<String, Serializable> map = readRootMap();
if (map == null)
saveObjectToFile((Serializable) rootMap);
else
rootMap = map;
} finally {
lock.unlock();
}
}
// Map
public void write(Object classInstance, String propertyKey, List<? extends Serializable> 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<String, ? extends Serializable> value) {
write(key, (Serializable) value);
}
public void write(String key, List<? extends Serializable> 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<String, Serializable> 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<String, Serializable> 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<String, Serializable>) object;
}
else {
log.error("Object is not type of Map<String, Serializable>");
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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<T extends Serializable> {
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
* <p/>
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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<T> {
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<Void> 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<Void>() {
@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();
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* The write operation used a background thread and supports a delayed write to avoid too many repeated write operations.
*/
public class Storage<T extends Serializable> {
private static final Logger log = LoggerFactory.getLogger(Storage.class);
public static final String DIR_KEY = "storage.dir";
private final File dir;
private FileManager<T> 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;
}
}

View file

@ -33,7 +33,6 @@ import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Peer; import io.bitsquare.p2p.Peer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.handlers.TradeResultHandler; import io.bitsquare.trade.handlers.TradeResultHandler;
import io.bitsquare.trade.handlers.TransactionResultHandler; import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.protocol.availability.CheckOfferAvailabilityModel; import io.bitsquare.trade.protocol.availability.CheckOfferAvailabilityModel;
@ -51,17 +50,16 @@ import io.bitsquare.user.User;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat; import org.bitcoinj.utils.Fiat;
import java.io.Serializable; import java.io.File;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -72,7 +70,6 @@ public class TradeManager {
private final User user; private final User user;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private final Persistence persistence;
private final MessageService messageService; private final MessageService messageService;
private MailboxService mailboxService; private MailboxService mailboxService;
private final AddressService addressService; private final AddressService addressService;
@ -81,14 +78,15 @@ public class TradeManager {
private final SignatureService signatureService; private final SignatureService signatureService;
private EncryptionService<MailboxMessage> encryptionService; private EncryptionService<MailboxMessage> encryptionService;
private final OfferBookService offerBookService; private final OfferBookService offerBookService;
private File storageDir;
private final Map<String, TakerAsSellerProtocol> takerAsSellerProtocolMap = new HashMap<>(); private final Map<String, TakerAsSellerProtocol> takerAsSellerProtocolMap = new HashMap<>();
private final Map<String, OffererAsBuyerProtocol> offererAsBuyerProtocolMap = new HashMap<>(); private final Map<String, OffererAsBuyerProtocol> offererAsBuyerProtocolMap = new HashMap<>();
private final Map<String, CheckOfferAvailabilityProtocol> checkOfferAvailabilityProtocolMap = new HashMap<>(); private final Map<String, CheckOfferAvailabilityProtocol> checkOfferAvailabilityProtocolMap = new HashMap<>();
private final ObservableList<Trade> openOfferTrades = FXCollections.observableArrayList(); private final TradesList openOfferTrades;
private final ObservableList<Trade> pendingTrades = FXCollections.observableArrayList(); private final TradesList pendingTrades;
private final ObservableList<Trade> closedTrades = FXCollections.observableArrayList(); private final TradesList closedTrades;
private final Map<String, MailboxMessage> mailboxMessages = new HashMap<>(); private final Map<String, MailboxMessage> mailboxMessages = new HashMap<>();
@ -97,13 +95,15 @@ public class TradeManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @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, MessageService messageService, MailboxService mailboxService, AddressService addressService, BlockChainService blockChainService,
WalletService walletService, SignatureService signatureService, EncryptionService<MailboxMessage> encryptionService, WalletService walletService, SignatureService signatureService, EncryptionService<MailboxMessage> encryptionService,
OfferBookService offerBookService) { OfferBookService offerBookService, @Named("storage.dir") File storageDir) {
this.user = user; this.user = user;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
this.persistence = persistence; this.openOfferTrades = openOfferTrades;
this.pendingTrades = pendingTrades;
this.closedTrades = closedTrades;
this.messageService = messageService; this.messageService = messageService;
this.mailboxService = mailboxService; this.mailboxService = mailboxService;
this.addressService = addressService; this.addressService = addressService;
@ -112,19 +112,7 @@ public class TradeManager {
this.signatureService = signatureService; this.signatureService = signatureService;
this.encryptionService = encryptionService; this.encryptionService = encryptionService;
this.offerBookService = offerBookService; this.offerBookService = offerBookService;
this.storageDir = storageDir;
Serializable openOffersObject = persistence.read(this, "openOffers");
if (openOffersObject instanceof List<?>) {
openOfferTrades.addAll((List<Trade>) openOffersObject);
}
Serializable pendingTradesObject = persistence.read(this, "pendingTrades");
if (pendingTradesObject instanceof List<?>) {
pendingTrades.addAll((List<Trade>) pendingTradesObject);
}
Serializable closedTradesObject = persistence.read(this, "closedTrades");
if (closedTradesObject instanceof List<?>) {
closedTrades.addAll((List<Trade>) closedTradesObject);
}
} }
@ -198,7 +186,6 @@ public class TradeManager {
Trade trade = new Trade(offer); Trade trade = new Trade(offer);
trade.setLifeCycleState(Trade.LifeCycleState.OPEN_OFFER); trade.setLifeCycleState(Trade.LifeCycleState.OPEN_OFFER);
openOfferTrades.add(trade); openOfferTrades.add(trade);
persistOpenOfferTrades();
createOffererAsBuyerProtocol(trade); createOffererAsBuyerProtocol(trade);
resultHandler.handleResult(transaction); resultHandler.handleResult(transaction);
@ -265,7 +252,7 @@ public class TradeManager {
// TODO remove if check when persistence is impl. // TODO remove if check when persistence is impl.
if (offererAsBuyerProtocolMap.containsKey(tradeId)) { if (offererAsBuyerProtocolMap.containsKey(tradeId)) {
offererAsBuyerProtocolMap.get(tradeId).onFiatPaymentStarted(); offererAsBuyerProtocolMap.get(tradeId).onFiatPaymentStarted();
persistPendingTrades(); // persistPendingTrades();
} }
} }
@ -276,9 +263,7 @@ public class TradeManager {
public void onWithdrawAtTradeCompleted(Trade trade) { public void onWithdrawAtTradeCompleted(Trade trade) {
trade.setLifeCycleState(Trade.LifeCycleState.COMPLETED); trade.setLifeCycleState(Trade.LifeCycleState.COMPLETED);
pendingTrades.remove(trade); pendingTrades.remove(trade);
persistPendingTrades();
closedTrades.add(trade); closedTrades.add(trade);
persistClosedTrades();
removeFromProtocolMap(trade); removeFromProtocolMap(trade);
} }
@ -297,15 +282,15 @@ public class TradeManager {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public ObservableList<Trade> getOpenOfferTrades() { public ObservableList<Trade> getOpenOfferTrades() {
return openOfferTrades; return openOfferTrades.getObservableList();
} }
public ObservableList<Trade> getPendingTrades() { public ObservableList<Trade> getPendingTrades() {
return pendingTrades; return pendingTrades.getObservableList();
} }
public ObservableList<Trade> getClosedTrades() { public ObservableList<Trade> getClosedTrades() {
return closedTrades; return closedTrades.getObservableList();
} }
@ -329,12 +314,10 @@ public class TradeManager {
if (result.isPresent()) { if (result.isPresent()) {
Trade trade = result.get(); Trade trade = result.get();
openOfferTrades.remove(trade); openOfferTrades.remove(trade);
persistOpenOfferTrades();
if (isCancelRequest) { if (isCancelRequest) {
trade.setLifeCycleState(Trade.LifeCycleState.CANCELED); trade.setLifeCycleState(Trade.LifeCycleState.CANCELED);
closedTrades.add(trade); closedTrades.add(trade);
persistClosedTrades();
} }
} }
@ -375,7 +358,6 @@ public class TradeManager {
trade.setTradingPeer(peer); trade.setTradingPeer(peer);
trade.setLifeCycleState(Trade.LifeCycleState.PENDING); trade.setLifeCycleState(Trade.LifeCycleState.PENDING);
pendingTrades.add(trade); pendingTrades.add(trade);
persistPendingTrades();
TakerAsSellerProtocol sellerTakesOfferProtocol = createTakerAsSellerProtocol(trade); TakerAsSellerProtocol sellerTakesOfferProtocol = createTakerAsSellerProtocol(trade);
//trade.setProtocol(sellerTakesOfferProtocol); //trade.setProtocol(sellerTakesOfferProtocol);
@ -397,7 +379,7 @@ public class TradeManager {
case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_STARTED:
case FIAT_PAYMENT_RECEIVED: case FIAT_PAYMENT_RECEIVED:
case PAYOUT_PUBLISHED: case PAYOUT_PUBLISHED:
persistPendingTrades(); // persistPendingTrades();
break; break;
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
case FAULT: case FAULT:
@ -418,7 +400,7 @@ public class TradeManager {
blockChainService, blockChainService,
signatureService, signatureService,
user, user,
persistence); storageDir);
TakerAsSellerProtocol protocol = new TakerAsSellerProtocol(model); TakerAsSellerProtocol protocol = new TakerAsSellerProtocol(model);
takerAsSellerProtocolMap.put(trade.getId(), protocol); takerAsSellerProtocolMap.put(trade.getId(), protocol);
@ -440,7 +422,7 @@ public class TradeManager {
blockChainService, blockChainService,
signatureService, signatureService,
user, user,
persistence); storageDir);
// TODO check, remove listener // TODO check, remove listener
@ -450,7 +432,7 @@ public class TradeManager {
case INIT: case INIT:
break; break;
case TAKE_OFFER_FEE_TX_CREATED: case TAKE_OFFER_FEE_TX_CREATED:
persistPendingTrades(); // persistPendingTrades();
break; break;
case DEPOSIT_PUBLISHED: case DEPOSIT_PUBLISHED:
removeOpenOffer(trade.getOffer(), removeOpenOffer(trade.getOffer(),
@ -459,13 +441,12 @@ public class TradeManager {
false); false);
model.trade.setLifeCycleState(Trade.LifeCycleState.PENDING); model.trade.setLifeCycleState(Trade.LifeCycleState.PENDING);
pendingTrades.add(trade); pendingTrades.add(trade);
persistPendingTrades();
break; break;
case DEPOSIT_CONFIRMED: case DEPOSIT_CONFIRMED:
case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_STARTED:
case FIAT_PAYMENT_RECEIVED: case FIAT_PAYMENT_RECEIVED:
case PAYOUT_PUBLISHED: case PAYOUT_PUBLISHED:
persistPendingTrades(); // persistPendingTrades();
break; break;
case TAKE_OFFER_FEE_PUBLISH_FAILED: case TAKE_OFFER_FEE_PUBLISH_FAILED:
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
@ -541,16 +522,4 @@ public class TradeManager {
}); });
} }
private void persistOpenOfferTrades() {
persistence.write(this, "openOfferTrades", (List<Trade>) new ArrayList<>(openOfferTrades));
}
private void persistPendingTrades() {
persistence.write(this, "pendingTrades", (List<Trade>) new ArrayList<>(pendingTrades));
}
private void persistClosedTrades() {
persistence.write(this, "closedTrades", (List<Trade>) new ArrayList<>(closedTrades));
}
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Trade> implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(TradesList.class);
transient final private Storage<TradesList> storage;
transient private ObservableList<Trade> observableList;
@Inject
public TradesList(Storage<TradesList> 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<Trade> getObservableList() {
return observableList;
}
}

View file

@ -26,7 +26,6 @@ import io.bitsquare.offer.Offer;
import io.bitsquare.p2p.MailboxMessage; import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.MessageService;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import java.io.Serializable; import java.io.Serializable;
@ -45,7 +44,6 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
transient public final WalletService walletService; transient public final WalletService walletService;
transient public final BlockChainService blockChainService; transient public final BlockChainService blockChainService;
transient public final SignatureService signatureService; transient public final SignatureService signatureService;
transient protected final Persistence persistence;
transient public MailboxMessage mailboxMessage; transient public MailboxMessage mailboxMessage;
@ -62,15 +60,13 @@ public class SharedTradeModel extends SharedTaskModel implements Serializable {
MailboxService mailboxService, MailboxService mailboxService,
WalletService walletService, WalletService walletService,
BlockChainService blockChainService, BlockChainService blockChainService,
SignatureService signatureService, SignatureService signatureService) {
Persistence persistence) {
this.offer = offer; this.offer = offer;
this.messageService = messageService; this.messageService = messageService;
this.mailboxService = mailboxService; this.mailboxService = mailboxService;
this.walletService = walletService; this.walletService = walletService;
this.blockChainService = blockChainService; this.blockChainService = blockChainService;
this.signatureService = signatureService; this.signatureService = signatureService;
this.persistence = persistence;
id = offer.getId(); id = offer.getId();
tradeWalletService = walletService.getTradeWalletService(); tradeWalletService = walletService.getTradeWalletService();

View file

@ -22,11 +22,12 @@ import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService; import io.bitsquare.crypto.SignatureService;
import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.MessageService;
import io.bitsquare.persistence.Persistence; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.SharedTradeModel; import io.bitsquare.trade.protocol.trade.SharedTradeModel;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -34,9 +35,11 @@ import org.slf4j.LoggerFactory;
public class OffererAsBuyerModel extends SharedTradeModel implements Serializable { public class OffererAsBuyerModel extends SharedTradeModel implements Serializable {
private static final long serialVersionUID = 5000457153390911569L; 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<OffererAsBuyerModel> storage;
transient public final Trade trade; transient public final Trade trade;
public final Taker taker; public final Taker taker;
public final Offerer offerer; public final Offerer offerer;
@ -50,26 +53,25 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl
BlockChainService blockChainService, BlockChainService blockChainService,
SignatureService signatureService, SignatureService signatureService,
User user, User user,
Persistence persistence) { File storageDir) {
super(trade.getOffer(), super(trade.getOffer(),
messageService, messageService,
mailboxService, mailboxService,
walletService, walletService,
blockChainService, blockChainService,
signatureService, signatureService);
persistence);
this.trade = trade; this.trade = trade;
this.storage = new Storage<>(storageDir);
Serializable serializable = persistence.read(this, "BuyerAsOffererModel_" + id);
if (serializable instanceof OffererAsBuyerModel) { OffererAsBuyerModel persisted = storage.initAndGetPersisted(this, getClass().getSimpleName() + id);
OffererAsBuyerModel persistedModel = (OffererAsBuyerModel) serializable; if (persisted != null) {
log.debug("Model reconstructed form persisted model."); log.debug("Model reconstructed form persisted model.");
setTakeOfferFeeTxId(persistedModel.takeOfferFeeTxId); setTakeOfferFeeTxId(persisted.takeOfferFeeTxId);
taker = persistedModel.taker; taker = persisted.taker;
offerer = persistedModel.offerer; offerer = persisted.offerer;
} }
else { else {
taker = new Taker(); taker = new Taker();
@ -90,13 +92,13 @@ public class OffererAsBuyerModel extends SharedTradeModel implements Serializabl
// Get called form taskRunner after each completed task // Get called form taskRunner after each completed task
@Override @Override
public void persist() { public void persist() {
persistence.write(this, "BuyerAsOffererModel_" + id, this); storage.save();
} }
@Override @Override
public void onComplete() { public void onComplete() {
// Just in case of successful completion we delete our persisted object // Just in case of successful completion we delete our persisted object
persistence.remove(this, "BuyerAsOffererModel_" + id); storage.remove();
} }
public String getTakeOfferFeeTxId() { public String getTakeOfferFeeTxId() {

View file

@ -22,13 +22,14 @@ import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService; import io.bitsquare.crypto.SignatureService;
import io.bitsquare.p2p.MailboxService; import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService; import io.bitsquare.p2p.MessageService;
import io.bitsquare.persistence.Persistence; import io.bitsquare.storage.Storage;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.SharedTradeModel; import io.bitsquare.trade.protocol.trade.SharedTradeModel;
import io.bitsquare.user.User; import io.bitsquare.user.User;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -36,9 +37,11 @@ import org.slf4j.LoggerFactory;
public class TakerAsSellerModel extends SharedTradeModel implements Serializable { public class TakerAsSellerModel extends SharedTradeModel implements Serializable {
private static final long serialVersionUID = -963501132927618376L; 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<TakerAsSellerModel> storage;
transient public final Trade trade;
public final Trade trade;
public final Taker taker; public final Taker taker;
public final Offerer offerer; public final Offerer offerer;
@ -52,28 +55,27 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
WalletService walletService, WalletService walletService,
BlockChainService blockChainService, BlockChainService blockChainService,
SignatureService signatureService, SignatureService signatureService,
User user, User user,
Persistence persistence) { File storageDir) {
super(trade.getOffer(), super(trade.getOffer(),
messageService, messageService,
mailboxService, mailboxService,
walletService, walletService,
blockChainService, blockChainService,
signatureService, signatureService);
persistence);
this.trade = trade; this.trade = trade;
this.storage = new Storage<>(storageDir);
Serializable serializable = persistence.read(this, "SellerAsTakerModel_" + id); TakerAsSellerModel persisted = storage.initAndGetPersisted(this, getClass().getSimpleName() + id);
if (serializable instanceof TakerAsSellerModel) { if (persisted != null) {
TakerAsSellerModel persistedModel = (TakerAsSellerModel) serializable; log.debug("Model reconstructed from persisted model.");
log.debug("Model reconstructed form persisted model.");
setTakeOfferFeeTx(persistedModel.getTakeOfferFeeTx()); setTakeOfferFeeTx(persisted.getTakeOfferFeeTx());
setPayoutTx(persistedModel.payoutTx); setPayoutTx(persisted.payoutTx);
taker = persistedModel.taker; taker = persisted.taker;
offerer = persistedModel.offerer; offerer = persisted.offerer;
} }
else { else {
taker = new Taker(); taker = new Taker();
@ -93,13 +95,13 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
// Get called form taskRunner after each completed task // Get called form taskRunner after each completed task
@Override @Override
public void persist() { public void persist() {
persistence.write(this, "SellerAsTakerModel_" + id, this); storage.save();
} }
@Override @Override
public void onComplete() { public void onComplete() {
// Just in case of successful completion we delete our persisted object // Just in case of successful completion we delete our persisted object
persistence.remove(this, "SellerAsTakerModel_" + id); storage.remove();
} }

View file

@ -21,7 +21,7 @@ import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.locale.Country; import io.bitsquare.locale.Country;
import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.LanguageUtil; import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -44,6 +44,7 @@ public class AccountSettings implements Serializable {
private List<Locale> acceptedLanguageLocales = new ArrayList<>(); private List<Locale> acceptedLanguageLocales = new ArrayList<>();
private List<Country> acceptedCountryLocales = new ArrayList<>(); private List<Country> acceptedCountryLocales = new ArrayList<>();
private List<Arbitrator> acceptedArbitrators = new ArrayList<>(); private List<Arbitrator> acceptedArbitrators = new ArrayList<>();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
@ -53,7 +54,7 @@ public class AccountSettings implements Serializable {
public AccountSettings(Storage<AccountSettings> storage, Arbitrator defaultArbitrator) { public AccountSettings(Storage<AccountSettings> storage, Arbitrator defaultArbitrator) {
this.storage = storage; this.storage = storage;
AccountSettings persisted = storage.getPersisted(this); AccountSettings persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
acceptedLanguageLocales = persisted.getAcceptedLanguageLocales(); acceptedLanguageLocales = persisted.getAcceptedLanguageLocales();
acceptedCountryLocales = persisted.getAcceptedCountries(); acceptedCountryLocales = persisted.getAcceptedCountries();
@ -106,7 +107,7 @@ public class AccountSettings implements Serializable {
storage.save(); storage.save();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -17,7 +17,7 @@
package io.bitsquare.user; package io.bitsquare.user;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import org.bitcoinj.utils.MonetaryFormat; import org.bitcoinj.utils.MonetaryFormat;
@ -53,13 +53,13 @@ public class Preferences implements Serializable {
private String _btcDenomination = MonetaryFormat.CODE_BTC; private String _btcDenomination = MonetaryFormat.CODE_BTC;
private Boolean _useAnimations = true; private Boolean _useAnimations = true;
private Boolean _useEffects = true; private Boolean _useEffects = true;
private Boolean displaySecurityDepositInfo = true;
// Observable wrappers // Observable wrappers
transient private final StringProperty btcDenomination = new SimpleStringProperty(_btcDenomination); transient private final StringProperty btcDenomination = new SimpleStringProperty(_btcDenomination);
transient private final BooleanProperty useAnimations = new SimpleBooleanProperty(_useAnimations); transient private final BooleanProperty useAnimations = new SimpleBooleanProperty(_useAnimations);
transient private final BooleanProperty useEffects = new SimpleBooleanProperty(_useEffects); transient private final BooleanProperty useEffects = new SimpleBooleanProperty(_useEffects);
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -68,11 +68,12 @@ public class Preferences implements Serializable {
public Preferences(Storage<Preferences> storage) { public Preferences(Storage<Preferences> storage) {
this.storage = storage; this.storage = storage;
Preferences persisted = storage.getPersisted(this); Preferences persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
setBtcDenomination(persisted._btcDenomination); setBtcDenomination(persisted._btcDenomination);
setUseAnimations(persisted._useAnimations); setUseAnimations(persisted._useAnimations);
setUseEffects(persisted._useEffects); 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 // 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); this.useEffects.set(useEffects);
} }
public void setDisplaySecurityDepositInfo(Boolean displaySecurityDepositInfo) {
this.displaySecurityDepositInfo = displaySecurityDepositInfo;
storage.save();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getter // Getter
@ -124,6 +130,10 @@ public class Preferences implements Serializable {
return useAnimations.get(); return useAnimations.get();
} }
public Boolean getDisplaySecurityDepositInfo() {
return displaySecurityDepositInfo;
}
public StringProperty btcDenominationProperty() { public StringProperty btcDenominationProperty() {
return btcDenomination; return btcDenomination;
} }
@ -135,4 +145,6 @@ public class Preferences implements Serializable {
public BooleanProperty useEffectsProperty() { public BooleanProperty useEffectsProperty() {
return useEffects; return useEffects;
} }
} }

View file

@ -20,7 +20,7 @@ package io.bitsquare.user;
import io.bitsquare.crypto.EncryptionService; import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.fiat.FiatAccount; import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.gui.components.Popups; import io.bitsquare.gui.components.Popups;
import io.bitsquare.persistence.Storage; import io.bitsquare.storage.Storage;
import java.io.Serializable; import java.io.Serializable;
@ -72,7 +72,7 @@ public class User implements Serializable {
this.storage = storage; this.storage = storage;
this.encryptionService = encryptionService; this.encryptionService = encryptionService;
User persisted = storage.getPersisted(this); User persisted = storage.initAndGetPersisted(this);
if (persisted != null) { if (persisted != null) {
p2pSigKeyPair = persisted.getP2pSigKeyPair(); p2pSigKeyPair = persisted.getP2pSigKeyPair();
p2pEncryptKeyPair = persisted.getP2pEncryptKeyPair(); p2pEncryptKeyPair = persisted.getP2pEncryptKeyPair();

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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));
}
}

View file

@ -44,7 +44,7 @@ public class CreateOfferViewModelTest {
BSFormatter formatter = new BSFormatter(new User()); BSFormatter formatter = new BSFormatter(new User());
formatter.setLocale(Locale.US); formatter.setLocale(Locale.US);
formatter.setFiatCurrencyCode("USD"); 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); presenter = new CreateOfferViewModel(model, new FiatValidator(null), new BtcValidator(), formatter);
} }

View file

@ -17,38 +17,21 @@
package io.bitsquare.trade.protocol.placeoffer; 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.btc.WalletService;
import io.bitsquare.offer.OfferBookService; 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.MessageService;
import io.bitsquare.p2p.Node;
import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder; import io.bitsquare.p2p.tomp2p.BootstrappedPeerBuilder;
import io.bitsquare.p2p.tomp2p.TomP2PMessageService;
import io.bitsquare.p2p.tomp2p.TomP2PNode; import io.bitsquare.p2p.tomp2p.TomP2PNode;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.user.User;
import org.bitcoinj.core.Address; import org.bitcoinj.core.Address;
import org.bitcoinj.utils.Threading;
import java.io.File; 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.junit.Ignore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import rx.Observable;
/** /**
* That test is ignored for automated testing as it needs custom setup. * That test is ignored for automated testing as it needs custom setup.
* <p/> * <p/>
@ -72,22 +55,19 @@ public class PlaceOfferProtocolTest {
private TomP2PNode tomP2PNode; private TomP2PNode tomP2PNode;
private BootstrappedPeerBuilder bootstrappedPeerBuilder; private BootstrappedPeerBuilder bootstrappedPeerBuilder;
@Before /* @Before
public void setup() throws InterruptedException { public void setup() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1); CountDownLatch countDownLatch = new CountDownLatch(1);
dir.mkdirs(); dir.mkdirs();
Persistence persistence = new Persistence(dir, "prefs");
persistence.init();
// messageService // messageService
Node bootstrapNode = Node.at("localhost", "127.0.0.1"); Node bootstrapNode = Node.at("localhost", "127.0.0.1");
User user = new User(); User user = new User();
/* try { *//* try {
user.initPersistedObject(); user.initPersistedObject();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
e.printStackTrace(); e.printStackTrace();
}*/ }*//*
bootstrappedPeerBuilder = new BootstrappedPeerBuilder(Node.DEFAULT_PORT, false, bootstrapNode, "<unspecified>"); bootstrappedPeerBuilder = new BootstrappedPeerBuilder(Node.DEFAULT_PORT, false, bootstrapNode, "<unspecified>");
tomP2PNode = new TomP2PNode(bootstrappedPeerBuilder); tomP2PNode = new TomP2PNode(bootstrappedPeerBuilder);
messageService = new TomP2PMessageService(tomP2PNode, null, null, null); messageService = new TomP2PMessageService(tomP2PNode, null, null, null);
@ -153,7 +133,7 @@ public class PlaceOfferProtocolTest {
public void shutDown() throws IOException, InterruptedException { public void shutDown() throws IOException, InterruptedException {
walletService.shutDown(); walletService.shutDown();
bootstrappedPeerBuilder.shutDown(); bootstrappedPeerBuilder.shutDown();
} }*/
/* @Test /* @Test
public void validateOfferTest() throws InterruptedException { public void validateOfferTest() throws InterruptedException {