/*
* 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.user;
import io.bitsquare.alert.Alert;
import io.bitsquare.app.Version;
import io.bitsquare.arbitration.Arbitrator;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.persistance.Persistable;
import io.bitsquare.filter.Filter;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.p2p.NodeAddress;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.storage.Storage;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.stream.Collectors;
/**
* The User is persisted locally.
* It must never be transmitted over the wire (messageKeyPair contains private key!).
*/
public final class User implements Persistable {
// That object is saved to disc. We need to take care of changes to not break deserialization.
private static final long serialVersionUID = Version.LOCAL_DB_VERSION;
private static final Logger log = LoggerFactory.getLogger(User.class);
// Transient immutable fields
transient final private Storage<User> storage;
transient private Set<TradeCurrency> tradeCurrenciesInPaymentAccounts;
// Persisted fields
private String accountID;
private Set<PaymentAccount> paymentAccounts = new HashSet<>();
private PaymentAccount currentPaymentAccount;
private List<String> acceptedLanguageLocaleCodes = new ArrayList<>();
private Alert developersAlert;
private Alert displayedAlert;
@Nullable
private Filter developersFilter;
private List<Arbitrator> acceptedArbitrators = new ArrayList<>();
@Nullable
private Arbitrator registeredArbitrator;
// Observable wrappers
transient final private ObservableSet<PaymentAccount> paymentAccountsAsObservable = FXCollections.observableSet(paymentAccounts);
transient final private ObjectProperty<PaymentAccount> currentPaymentAccountProperty = new SimpleObjectProperty<>(currentPaymentAccount);
@Inject
public User(Storage<User> storage, KeyRing keyRing) throws NoSuchAlgorithmException {
this.storage = storage;
User persisted = storage.initAndGetPersisted(this);
if (persisted != null) {
accountID = persisted.getAccountId();
// The check is only needed to not break old versions where paymentAccounts was not included and is null,
// Can be removed later
if (persisted.getPaymentAccounts() != null)
paymentAccounts = new HashSet<>(persisted.getPaymentAccounts());
paymentAccountsAsObservable.addAll(paymentAccounts);
currentPaymentAccount = persisted.getCurrentPaymentAccount();
currentPaymentAccountProperty.set(currentPaymentAccount);
acceptedLanguageLocaleCodes = persisted.getAcceptedLanguageLocaleCodes();
if (persisted.getAcceptedArbitrators() != null)
acceptedArbitrators = persisted.getAcceptedArbitrators();
registeredArbitrator = persisted.getRegisteredArbitrator();
developersAlert = persisted.getDevelopersAlert();
displayedAlert = persisted.getDisplayedAlert();
developersFilter = persisted.getDevelopersFilter();
} else {
accountID = String.valueOf(Math.abs(keyRing.getPubKeyRing().hashCode()));
acceptedLanguageLocaleCodes.add(LanguageUtil.getDefaultLanguageLocaleAsCode());
String english = LanguageUtil.getEnglishLanguageLocaleCode();
if (!acceptedLanguageLocaleCodes.contains(english))
acceptedLanguageLocaleCodes.add(english);
acceptedArbitrators = new ArrayList<>();
}
storage.queueUpForSave();
// Use that to guarantee update of the serializable field and to make a storage update in case of a change
paymentAccountsAsObservable.addListener((SetChangeListener<PaymentAccount>) change -> {
paymentAccounts = new HashSet<>(paymentAccountsAsObservable);
tradeCurrenciesInPaymentAccounts = paymentAccounts.stream().flatMap(e -> e.getTradeCurrencies().stream()).collect(Collectors.toSet());
storage.queueUpForSave();
});
currentPaymentAccountProperty.addListener((ov) -> {
currentPaymentAccount = currentPaymentAccountProperty.get();
storage.queueUpForSave();
});
tradeCurrenciesInPaymentAccounts = paymentAccounts.stream().flatMap(e -> e.getTradeCurrencies().stream()).collect(Collectors.toSet());
}
// for unit tests
public User() {
this.storage = null;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
try {
in.defaultReadObject();
} catch (Throwable t) {
log.trace("Cannot be deserialized." + t.getMessage());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Public Methods
///////////////////////////////////////////////////////////////////////////////////////////
public void addPaymentAccount(PaymentAccount paymentAccount) {
paymentAccountsAsObservable.add(paymentAccount);
setCurrentPaymentAccount(paymentAccount);
}
public void removePaymentAccount(PaymentAccount paymentAccount) {
paymentAccountsAsObservable.remove(paymentAccount);
}
public void setCurrentPaymentAccount(PaymentAccount paymentAccount) {
currentPaymentAccountProperty.set(paymentAccount);
}
public boolean addAcceptedLanguageLocale(String localeCode) {
if (!acceptedLanguageLocaleCodes.contains(localeCode)) {
boolean changed = acceptedLanguageLocaleCodes.add(localeCode);
if (changed)
storage.queueUpForSave();
return changed;
} else {
return false;
}
}
public boolean removeAcceptedLanguageLocale(String languageLocaleCode) {
boolean changed = acceptedLanguageLocaleCodes.remove(languageLocaleCode);
if (changed)
storage.queueUpForSave();
return changed;
}
public void addAcceptedArbitrator(Arbitrator arbitrator) {
if (!acceptedArbitrators.contains(arbitrator) && !isMyOwnRegisteredArbitrator(arbitrator)) {
boolean changed = acceptedArbitrators.add(arbitrator);
if (changed)
storage.queueUpForSave();
}
}
public boolean isMyOwnRegisteredArbitrator(Arbitrator arbitrator) {
return arbitrator.equals(registeredArbitrator);
}
public void removeAcceptedArbitrator(Arbitrator arbitrator) {
boolean changed = acceptedArbitrators.remove(arbitrator);
if (changed)
storage.queueUpForSave();
}
public void clearAcceptedArbitrators() {
acceptedArbitrators.clear();
storage.queueUpForSave();
}
public void setRegisteredArbitrator(@Nullable Arbitrator arbitrator) {
this.registeredArbitrator = arbitrator;
storage.queueUpForSave();
}
public void setDevelopersFilter(Filter developersFilter) {
this.developersFilter = developersFilter;
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
@Nullable
public PaymentAccount getPaymentAccount(String paymentAccountId) {
Optional<PaymentAccount> optional = paymentAccounts.stream().filter(e -> e.getId().equals(paymentAccountId)).findAny();
if (optional.isPresent())
return optional.get();
else
return null;
}
public String getAccountId() {
return accountID;
}
/* public boolean isRegistered() {
return getAccountId() != null;
}*/
private PaymentAccount getCurrentPaymentAccount() {
return currentPaymentAccount;
}
public ObjectProperty<PaymentAccount> currentPaymentAccountProperty() {
return currentPaymentAccountProperty;
}
public Set<PaymentAccount> getPaymentAccounts() {
return paymentAccounts;
}
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
return paymentAccountsAsObservable;
}
@Nullable
public Arbitrator getRegisteredArbitrator() {
return registeredArbitrator;
}
public List<Arbitrator> getAcceptedArbitrators() {
return acceptedArbitrators;
}
public List<NodeAddress> getAcceptedArbitratorAddresses() {
return acceptedArbitrators.stream().map(Arbitrator::getArbitratorNodeAddress).collect(Collectors.toList());
}
public List<String> getAcceptedLanguageLocaleCodes() {
return acceptedLanguageLocaleCodes != null ? acceptedLanguageLocaleCodes : new ArrayList<>();
}
/* public List<String> getArbitratorAddresses(List<String> idList) {
List<String> receiverAddresses = new ArrayList<>();
for (Arbitrator arbitrator : getAcceptedArbitrators()) {
for (String id : idList) {
if (id.equals(arbitrator.getId()))
receiverAddresses.add(arbitrator.getBtcAddress());
}
}
return receiverAddresses;
}*/
public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) {
Optional<Arbitrator> arbitratorOptional = acceptedArbitrators.stream()
.filter(e -> e.getArbitratorNodeAddress().equals(nodeAddress))
.findFirst();
if (arbitratorOptional.isPresent())
return arbitratorOptional.get();
else
return null;
}
@org.jetbrains.annotations.Nullable
public Filter getDevelopersFilter() {
return developersFilter;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Utils
///////////////////////////////////////////////////////////////////////////////////////////
/* public Optional<TradeCurrency> getPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
return getPaymentAccounts().stream()
.flatMap(e -> e.getTradeCurrencies().stream())
.filter(e -> e.equals(tradeCurrency))
.findFirst();
}*/
@Nullable
public PaymentAccount findFirstPaymentAccountWithCurrency(TradeCurrency tradeCurrency) {
for (PaymentAccount paymentAccount : paymentAccounts) {
for (TradeCurrency tradeCurrency1 : paymentAccount.getTradeCurrencies()) {
if (tradeCurrency1.equals(tradeCurrency))
return paymentAccount;
}
}
return null;
}
public boolean hasMatchingLanguage(Arbitrator arbitrator) {
if (arbitrator != null) {
for (String acceptedCode : acceptedLanguageLocaleCodes) {
for (String itemCode : arbitrator.getLanguageCodes()) {
if (acceptedCode.equals(itemCode))
return true;
}
}
}
return false;
}
public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) {
return findFirstPaymentAccountWithCurrency(tradeCurrency) != null;
}
public void setDevelopersAlert(Alert developersAlert) {
this.developersAlert = developersAlert;
storage.queueUpForSave();
}
public Alert getDevelopersAlert() {
return developersAlert;
}
public void setDisplayedAlert(Alert displayedAlert) {
this.displayedAlert = displayedAlert;
storage.queueUpForSave();
}
public Alert getDisplayedAlert() {
return displayedAlert;
}
}