diff --git a/application/src/main/java/bisq/application/DefaultApplicationService.java b/application/src/main/java/bisq/application/DefaultApplicationService.java index 9aca930a0b..a69d461492 100644 --- a/application/src/main/java/bisq/application/DefaultApplicationService.java +++ b/application/src/main/java/bisq/application/DefaultApplicationService.java @@ -21,6 +21,7 @@ import bisq.chat.ChatService; import bisq.common.observable.Observable; import bisq.common.threading.ExecutorFactory; +import bisq.common.util.CompletableFutureUtils; import bisq.identity.IdentityService; import bisq.network.NetworkService; import bisq.network.NetworkServiceConfig; @@ -56,11 +57,12 @@ public class DefaultApplicationService extends ApplicationService { public static final ExecutorService DISPATCHER = ExecutorFactory.newSingleThreadExecutor("DefaultApplicationService.dispatcher"); public enum State { - NEW, - START_NETWORK, - NETWORK_STARTED, - INITIALIZE_COMPLETED, - INITIALIZE_FAILED + INITIALIZE_APP, + INITIALIZE_NETWORK, + INITIALIZE_WALLET, + INITIALIZE_SERVICES, + APP_INITIALIZED, + FAILED } private final SecurityService securityService; @@ -76,7 +78,7 @@ public enum State { private final ProtocolService protocolService; private final SupportService supportService; - private final Observable state = new Observable<>(State.NEW); + private final Observable state = new Observable<>(State.INITIALIZE_APP); public DefaultApplicationService(String[] args) { super("default", args); @@ -119,21 +121,34 @@ public DefaultApplicationService(String[] args) { protocolService = new ProtocolService(networkService, identityService, persistenceService, offerService.getOpenOfferService()); } - - // At the moment we do not initialize in parallel to keep thing simple, but can be optimized later @Override public CompletableFuture initialize() { return securityService.initialize() - .thenCompose(result -> walletService.initialize()) - .whenComplete((result, throwable) -> { + .thenCompose(result -> { + setState(State.INITIALIZE_NETWORK); + + CompletableFuture networkFuture = networkService.initialize(); + CompletableFuture walletFuture = walletService.initialize(); + + networkFuture.whenComplete((r, throwable) -> { + if (throwable != null) { + log.error("Error at networkFuture.initialize", throwable); + } else if (!walletFuture.isDone()) { + setState(State.INITIALIZE_WALLET); + } + }); + walletFuture.whenComplete((r, throwable) -> { + if (throwable != null) { + log.error("Error at walletService.initialize", throwable); + } + }); + return CompletableFutureUtils.allOf(walletFuture, networkFuture).thenApply(list -> true); + }) + .whenComplete((r, throwable) -> { if (throwable == null) { - setState(State.START_NETWORK); - } else { - log.error("Error at walletService.initialize", throwable); + setState(State.INITIALIZE_SERVICES); } }) - .thenCompose(result -> networkService.initialize()) - .whenComplete((r, t) -> setState(State.NETWORK_STARTED)) .thenCompose(result -> identityService.initialize()) .thenCompose(result -> oracleService.initialize()) .thenCompose(result -> accountService.initialize()) @@ -145,12 +160,17 @@ public CompletableFuture initialize() { .thenCompose(result -> protocolService.initialize()) .orTimeout(5, TimeUnit.MINUTES) .whenComplete((success, throwable) -> { - if (success) { - setState(State.INITIALIZE_COMPLETED); - log.info("NetworkApplicationService initialized"); + if (throwable == null) { + if (success) { + setState(State.APP_INITIALIZED); + log.info("ApplicationService initialized"); + } else { + setState(State.FAILED); + log.error("Initializing applicationService failed"); + } } else { - setState(State.INITIALIZE_FAILED); - log.error("Initializing networkApplicationService failed", throwable); + setState(State.FAILED); + log.error("Initializing applicationService failed", throwable); } }); } diff --git a/application/src/main/java/bisq/application/Executable.java b/application/src/main/java/bisq/application/Executable.java index 9c5c0bb176..d3d11bbd73 100644 --- a/application/src/main/java/bisq/application/Executable.java +++ b/application/src/main/java/bisq/application/Executable.java @@ -22,8 +22,12 @@ protected void launchApplication(String[] args) { protected void onApplicationLaunched() { applicationService.initialize() .whenComplete((success, throwable) -> { - if (success) { - onDomainInitialized(); + if (throwable == null) { + if (success) { + onDomainInitialized(); + } else { + log.error("Initialize applicationService failed", throwable); + } } else { onInitializeDomainFailed(throwable); } @@ -31,7 +35,7 @@ protected void onApplicationLaunched() { } protected void onInitializeDomainFailed(Throwable throwable) { - throwable.printStackTrace(); + log.error("Initialize domain failed", throwable); } abstract protected void onDomainInitialized(); diff --git a/common/src/main/java/bisq/common/util/OperatingSystem.java b/common/src/main/java/bisq/common/util/OperatingSystem.java new file mode 100644 index 0000000000..0768b04fb4 --- /dev/null +++ b/common/src/main/java/bisq/common/util/OperatingSystem.java @@ -0,0 +1,24 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.common.util; + +public enum OperatingSystem { + LINUX, + MAC, + WIN +} diff --git a/common/src/main/java/bisq/common/util/OsUtils.java b/common/src/main/java/bisq/common/util/OsUtils.java index a387b1f1e7..cf069674bc 100644 --- a/common/src/main/java/bisq/common/util/OsUtils.java +++ b/common/src/main/java/bisq/common/util/OsUtils.java @@ -37,7 +37,7 @@ public static File getUserDataDir() { return new File(System.getenv("APPDATA")); } - if (isOSX()) { + if (isMac()) { return Paths.get(System.getProperty("user.home"), "Library", "Application Support").toFile(); } @@ -53,7 +53,7 @@ public static boolean isWindows() { return getOSName().contains("win"); } - public static boolean isOSX() { + public static boolean isMac() { return getOSName().contains("mac") || getOSName().contains("darwin"); } @@ -97,6 +97,19 @@ public static String getOSName() { return System.getProperty("os.name").toLowerCase(Locale.US); } + public static OperatingSystem getOperatingSystem() { + if (isLinux()) { + return OperatingSystem.LINUX; + } else if (isMac()) { + return OperatingSystem.MAC; + } else if (isWindows()) { + return OperatingSystem.WIN; + } else { + throw new UnsupportedOperationException("Unsupported operating system: " + OsUtils.getOSName()); + } + } + + public static void makeBinaryExecutable(Path binaryPath) { boolean isSuccess = binaryPath.toFile().setExecutable(true); if (!isSuccess) { @@ -117,7 +130,7 @@ public static boolean open(String fileName) { if (runCommand("xdg-open", "%s", fileName)) return true; } - if (isOSX()) { + if (isMac()) { if (runCommand("open", "%s", fileName)) return true; } diff --git a/desktop/src/main/java/bisq/desktop/primary/PrimaryStageController.java b/desktop/src/main/java/bisq/desktop/primary/PrimaryStageController.java index c590301281..eb3c64025f 100644 --- a/desktop/src/main/java/bisq/desktop/primary/PrimaryStageController.java +++ b/desktop/src/main/java/bisq/desktop/primary/PrimaryStageController.java @@ -20,6 +20,7 @@ import bisq.application.DefaultApplicationService; import bisq.desktop.common.Browser; import bisq.desktop.common.JavaFxApplicationData; +import bisq.desktop.common.threading.UIThread; import bisq.desktop.common.utils.Transitions; import bisq.desktop.common.view.Controller; import bisq.desktop.common.view.Navigation; @@ -27,10 +28,14 @@ import bisq.desktop.common.view.NavigationTarget; import bisq.desktop.components.overlay.Notification; import bisq.desktop.components.overlay.Overlay; +import bisq.desktop.components.overlay.Popup; import bisq.desktop.primary.main.MainController; import bisq.desktop.primary.overlay.OverlayController; import bisq.desktop.primary.splash.SplashController; -import bisq.settings.*; +import bisq.settings.Cookie; +import bisq.settings.CookieKey; +import bisq.settings.DontShowAgainService; +import bisq.settings.SettingsService; import javafx.application.Platform; import javafx.geometry.Rectangle2D; import javafx.scene.layout.AnchorPane; @@ -148,15 +153,17 @@ public void onDomainInitialized() { } public void onUncaughtException(Thread thread, Throwable throwable) { - // todo show error popup + log.error("Uncaught exception from thread {}", thread); + log.error("Uncaught exception", throwable); + UIThread.run(() -> new Popup().error(throwable.getMessage()).show()); } public void onQuit() { shutdown(); } - public void onInitializeDomainFailed() { - //todo show error popup + public void onInitializeDomainFailed(Throwable throwable) { + new Popup().error(throwable.getMessage()).show(); } public void shutdown() { diff --git a/desktop/src/main/java/bisq/desktop/primary/PrimaryStageView.java b/desktop/src/main/java/bisq/desktop/primary/PrimaryStageView.java index 30f7c9e0a5..a593e039a1 100644 --- a/desktop/src/main/java/bisq/desktop/primary/PrimaryStageView.java +++ b/desktop/src/main/java/bisq/desktop/primary/PrimaryStageView.java @@ -119,7 +119,7 @@ private void configKeyEventHandlers() { private Image getApplicationIconImage() { String iconPath; - if (OsUtils.isOSX()) + if (OsUtils.isMac()) iconPath = ImageUtil.isRetina() ? "images/window_icon@2x.png" : "images/window_icon.png"; else if (OsUtils.isWindows()) iconPath = "images/task_bar_icon_windows.png"; diff --git a/desktop/src/main/java/bisq/desktop/primary/main/content/settings/userProfile/UserProfileController.java b/desktop/src/main/java/bisq/desktop/primary/main/content/settings/userProfile/UserProfileController.java index 4bb778df65..e95dce5211 100644 --- a/desktop/src/main/java/bisq/desktop/primary/main/content/settings/userProfile/UserProfileController.java +++ b/desktop/src/main/java/bisq/desktop/primary/main/content/settings/userProfile/UserProfileController.java @@ -156,7 +156,7 @@ private CompletableFuture doDelete() { return userIdentityService.deleteUserProfile(userIdentityService.getSelectedUserIdentity().get()) .whenComplete((result, throwable) -> { if (throwable != null) { - new Popup().error(throwable.getMessage()).show(); + UIThread.run(() -> new Popup().error(throwable.getMessage()).show()); } else { if (!model.getUserIdentities().isEmpty()) { UIThread.runOnNextRenderFrame(() -> { diff --git a/desktopapp/src/main/java/bisq/desktopapp/JavaFxExecutable.java b/desktopapp/src/main/java/bisq/desktopapp/JavaFxExecutable.java index 3081e8e077..90efe66163 100644 --- a/desktopapp/src/main/java/bisq/desktopapp/JavaFxExecutable.java +++ b/desktopapp/src/main/java/bisq/desktopapp/JavaFxExecutable.java @@ -21,6 +21,7 @@ import bisq.application.Executable; import bisq.common.annotations.LateInit; import bisq.desktop.common.threading.UIThread; +import bisq.desktop.components.overlay.Popup; import bisq.desktop.primary.PrimaryStageController; import javafx.application.Application; import javafx.application.Platform; @@ -80,7 +81,12 @@ protected void onDomainInitialized() { @Override protected void onInitializeDomainFailed(Throwable throwable) { super.onInitializeDomainFailed(throwable); - requireNonNull(primaryStageController).onInitializeDomainFailed(); + + if (primaryStageController == null) { + UIThread.run(() -> new Popup().error(throwable.getMessage()).show()); + } else { + UIThread.run(() -> primaryStageController.onInitializeDomainFailed(throwable)); + } } @Override diff --git a/i18n/src/main/resources/default.properties b/i18n/src/main/resources/default.properties index b1f7f3d985..de01752a87 100644 --- a/i18n/src/main/resources/default.properties +++ b/i18n/src/main/resources/default.properties @@ -563,11 +563,12 @@ bisqEasy.mediation.msgToPeer=Your trade peer requested mediation support. I am a ###################################################### ## ApplicationService ###################################################### -applicationService.state.NEW=Starting application -applicationService.state.START_NETWORK=Bootstrap to network -applicationService.state.NETWORK_STARTED=Network bootstrapped -applicationService.state.INITIALIZE_COMPLETED=Initialization complete -applicationService.state.INITIALIZE_FAILED=Initialization failed +applicationService.state.INITIALIZE_APP=Starting Bisq +applicationService.state.INITIALIZE_NETWORK=Initialize P2P network +applicationService.state.INITIALIZE_WALLET=Initialize wallet +applicationService.state.INITIALIZE_SERVICES=Initialize services +applicationService.state.APP_INITIALIZED=Bisq started +applicationService.state.FAILED=Startup failed ###################################################### @@ -748,7 +749,7 @@ trade.protocols.MONERO_SWAP.tradeOffs=\ - It requires some technical skills and experience to run the required infrastructure\n\ - Confirmation time is based on the confirmations on both blockchains which will be usually about 20-30 min.\n\ - Transaction fees on the Bitcoin side could be non-trivial in case of high blockchain congestion (rather rare, though)\n\ - - Both traders need to be online + - Both traders need to be online\n\ - It requires lots of fast disk storage for the nodes diff --git a/network/tor/src/main/java/bisq/tor/OsType.java b/network/tor/src/main/java/bisq/tor/OsType.java index d1b9db726c..5045765848 100644 --- a/network/tor/src/main/java/bisq/tor/OsType.java +++ b/network/tor/src/main/java/bisq/tor/OsType.java @@ -35,7 +35,7 @@ public enum OsType { public static OsType getOsType() { if (OsUtils.isWindows()) { return WIN; - } else if (OsUtils.isOSX()) { + } else if (OsUtils.isMac()) { return OSX; } else if (OsUtils.isLinux32()) { return LINUX_32; diff --git a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumBinaryExtractor.java b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumBinaryExtractor.java index 405ed39fb8..76e4ba46a3 100644 --- a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumBinaryExtractor.java +++ b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumBinaryExtractor.java @@ -52,7 +52,7 @@ public Path extractFileWithSuffix(String fileNameSuffix) { createDestDirIfNotPresent(); try (InputStream inputStream = openBinariesZipAsStream()) { - if (OsUtils.isOSX()) { + if (OsUtils.isMac()) { extractElectrumAppFileToDataDir(inputStream); return destDir.toPath().resolve("Electrum.app"); diff --git a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumProcess.java b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumProcess.java index 313d29af70..95d2e0d1a4 100644 --- a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumProcess.java +++ b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumProcess.java @@ -22,17 +22,19 @@ import bisq.wallets.electrum.rpc.ElectrumProcessConfig; import bisq.wallets.process.BisqProcess; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import java.nio.file.Path; import java.util.Optional; +@Slf4j public class ElectrumProcess implements BisqProcess { private final Path electrumRootDataDir; private final ElectrumProcessConfig processConfig; @Getter - private Optional binaryPath = Optional.empty(); + private final Path binaryPath; private Optional electrumRegtestProcess = Optional.empty(); private Optional electrumVersion = Optional.empty(); @@ -40,13 +42,16 @@ public class ElectrumProcess implements BisqProcess { public ElectrumProcess(Path electrumRootDataDir, ElectrumProcessConfig processConfig) { this.electrumRootDataDir = electrumRootDataDir; this.processConfig = processConfig; + binaryPath = resolveBinaryPath(); } @Override public void start() { - unpackArchive(); - if (OsUtils.isLinux()) { - OsUtils.makeBinaryExecutable(binaryPath.orElseThrow()); + if (!binaryPath.toFile().exists()) { + unpackArchive(); + if (OsUtils.isLinux()) { + OsUtils.makeBinaryExecutable(binaryPath); + } } createAndStartProcess(); } @@ -59,20 +64,12 @@ public void shutdown() { private void unpackArchive() { Path destDirPath = electrumRootDataDir.resolve("bin"); var binaryExtractor = new ElectrumBinaryExtractor(destDirPath); - String binarySuffix = getBinarySuffix(); - Path extractedFilePath = binaryExtractor.extractFileWithSuffix(binarySuffix); - - if (OsUtils.isOSX()) { - extractedFilePath = extractedFilePath.resolve("Contents/MacOS/run_electrum"); - } - - binaryPath = Optional.of(extractedFilePath); + binaryExtractor.extractFileWithSuffix(binarySuffix); } private void createAndStartProcess() { - Path path = binaryPath.orElseThrow(); - var process = new ElectrumRegtestProcess(path, processConfig); + var process = new ElectrumRegtestProcess(binaryPath, processConfig); process.start(); electrumRegtestProcess = Optional.of(process); } @@ -82,29 +79,28 @@ public Optional getElectrumVersion() { return electrumVersion; } - if (binaryPath.isPresent()) { - Path path = binaryPath.get(); - String fileName = path.getFileName().toString(); + String fileName = binaryPath.getFileName().toString(); - // File name: electrum-4.2.2.dmg / electrum-4.2.2.exe / electrum-4.2.2-x86_64.AppImage - String secondPart = fileName.split("-")[1]; - secondPart = secondPart.replace(".dmg", ""); - electrumVersion = Optional.of(secondPart); - } + // File name: electrum-4.2.2.dmg / electrum-4.2.2.exe / electrum-4.2.2-x86_64.AppImage + String secondPart = fileName.split("-")[1]; + secondPart = secondPart.replace(".dmg", "") + .replace(".exe", ""); + electrumVersion = Optional.of(secondPart); return Optional.empty(); } public static String getBinarySuffix() { - if (OsUtils.isLinux()) { - return ElectrumBinaryExtractor.LINUX_BINARY_SUFFIX; - } else if (OsUtils.isOSX()) { - return ElectrumBinaryExtractor.MAC_OS_BINARY_SUFFIX; - } else if (OsUtils.isWindows()) { - return ElectrumBinaryExtractor.WINDOWS_BINARY_SUFFIX; + switch (OsUtils.getOperatingSystem()) { + case LINUX: + return ElectrumBinaryExtractor.LINUX_BINARY_SUFFIX; + case MAC: + return ElectrumBinaryExtractor.MAC_OS_BINARY_SUFFIX; + case WIN: + return ElectrumBinaryExtractor.WINDOWS_BINARY_SUFFIX; + default: + throw new UnsupportedOperationException("Bisq is running on an unsupported OS: " + OsUtils.getOSName()); } - - throw new UnsupportedOperationException("Bisq is running on an unsupported OS: " + OsUtils.getOSName()); } public Path getDataDir() { @@ -114,4 +110,14 @@ public Path getDataDir() { public ElectrumDaemon getElectrumDaemon() { return electrumRegtestProcess.orElseThrow().getElectrumDaemon(); } + + private Path resolveBinaryPath() { + Path destDirPath = electrumRootDataDir.resolve("bin"); + String binarySuffix = getBinarySuffix(); + Path extractedFilePath = destDirPath.resolve("Electrum." + binarySuffix); + if (OsUtils.isMac()) { + extractedFilePath = extractedFilePath.resolve("Contents/MacOS/run_electrum"); + } + return extractedFilePath; + } } diff --git a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumWalletService.java b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumWalletService.java index 5575366194..46e49563d1 100644 --- a/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumWalletService.java +++ b/wallets/electrum/src/main/java/bisq/wallets/electrum/ElectrumWalletService.java @@ -63,6 +63,7 @@ public CompletableFuture initialize() { log.info("initialize"); return CompletableFuture.supplyAsync(() -> { + long ts = System.currentTimeMillis(); electrumProcess.start(); electrumWallet = new ElectrumWallet( @@ -73,7 +74,10 @@ public CompletableFuture initialize() { electrumProcess.getElectrumDaemon(), new ObservableSet<>() ); + + // TODO pw support electrumWallet.initialize(Optional.empty()); + log.info("Electrum wallet initialized after {} ms.", System.currentTimeMillis() - ts); updateBalance(); return true; @@ -97,6 +101,8 @@ public CompletableFuture shutdown() { public CompletableFuture getNewAddress() { return CompletableFuture.supplyAsync(() -> { String receiveAddress = electrumWallet.getNewAddress(); + + // Do we need persistence? receiveAddresses.add(receiveAddress); return receiveAddress; }); diff --git a/wallets/electrum/src/main/java/bisq/wallets/electrum/FileCreationWatcher.java b/wallets/electrum/src/main/java/bisq/wallets/electrum/FileCreationWatcher.java index cad9e71bea..fda1287ce3 100644 --- a/wallets/electrum/src/main/java/bisq/wallets/electrum/FileCreationWatcher.java +++ b/wallets/electrum/src/main/java/bisq/wallets/electrum/FileCreationWatcher.java @@ -17,6 +17,8 @@ package bisq.wallets.electrum; +import com.sun.nio.file.SensitivityWatchEventModifier; + import java.io.IOException; import java.nio.file.*; import java.util.concurrent.ExecutorService; @@ -38,8 +40,9 @@ public Future waitUntilNewFileCreated() { private Path waitForNewFile() throws IOException, InterruptedException { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { - directoryToWatch.register(watchService, StandardWatchEventKinds.ENTRY_CREATE); - + directoryToWatch.register(watchService, + new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_CREATE}, + SensitivityWatchEventModifier.HIGH); WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES); for (WatchEvent event : watchKey.pollEvents()) { WatchEvent.Kind kind = event.kind(); diff --git a/wallets/process/src/main/java/bisq/wallets/process/DaemonProcess.java b/wallets/process/src/main/java/bisq/wallets/process/DaemonProcess.java index 328fbbae42..21c39b82eb 100644 --- a/wallets/process/src/main/java/bisq/wallets/process/DaemonProcess.java +++ b/wallets/process/src/main/java/bisq/wallets/process/DaemonProcess.java @@ -51,7 +51,6 @@ public void start() { FileUtils.makeDirs(dataDir.toFile()); process = createAndStartProcess(); waitUntilReady(); - } catch (IOException e) { throw new WalletStartupFailedException("Cannot start wallet process.", e); }