diff --git a/api/src/test/java/io/xpipe/api/test/StartupTest.java b/api/src/test/java/io/xpipe/api/test/StartupTest.java index b8de3e385..7c185dd27 100644 --- a/api/src/test/java/io/xpipe/api/test/StartupTest.java +++ b/api/src/test/java/io/xpipe/api/test/StartupTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; public class StartupTest { @Test - public void test( ) throws Exception { + public void test() throws Exception { BeaconDaemonController.start(XPipeDaemonMode.TRAY); BeaconDaemonController.stop(); } diff --git a/app/src/localTest/java/module-info.java b/app/src/localTest/java/module-info.java index 9c44005d6..b5f43897b 100644 --- a/app/src/localTest/java/module-info.java +++ b/app/src/localTest/java/module-info.java @@ -1,4 +1,4 @@ open module io.xpipe.app.localTest { requires org.junit.jupiter.api; requires io.xpipe.app; -} \ No newline at end of file +} diff --git a/app/src/localTest/java/test/Test.java b/app/src/localTest/java/test/Test.java index c11a10b62..2e75f5fb3 100644 --- a/app/src/localTest/java/test/Test.java +++ b/app/src/localTest/java/test/Test.java @@ -9,6 +9,5 @@ public class Test extends LocalExtensionTest { public void test() { System.out.println("a"); System.out.println(DataStorage.get().getStoreEntries()); - } } diff --git a/app/src/main/java/io/xpipe/app/Main.java b/app/src/main/java/io/xpipe/app/Main.java index 71de2842d..978ed9281 100644 --- a/app/src/main/java/io/xpipe/app/Main.java +++ b/app/src/main/java/io/xpipe/app/Main.java @@ -14,10 +14,10 @@ public class Main { // Since this is not marked as a console application, it will not print anything when you run it in a console // So sadly there can't be a help command -// if (args.length == 1 && args[0].equals("--help")) { -// System.out.println("HELP"); -// return; -// } + // if (args.length == 1 && args[0].equals("--help")) { + // System.out.println("HELP"); + // return; + // } OperationMode.init(args); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java b/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java index a4bd97114..487dbd46f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserAlerts.java @@ -14,14 +14,6 @@ import java.util.stream.Collectors; public class BrowserAlerts { - public enum FileConflictChoice { - CANCEL, - SKIP, - SKIP_ALL, - REPLACE, - REPLACE_ALL - } - public static FileConflictChoice showFileConflictAlert(String file, boolean multiple) { var map = new LinkedHashMap(); map.put(new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE), FileConflictChoice.CANCEL); @@ -36,10 +28,14 @@ public class BrowserAlerts { return AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("fileConflictAlertTitle")); alert.setHeaderText(AppI18n.get("fileConflictAlertHeader")); - AppWindowHelper.setContent(alert, AppI18n.get(multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file)); + AppWindowHelper.setContent( + alert, + AppI18n.get( + multiple ? "fileConflictAlertContentMultiple" : "fileConflictAlertContent", file)); alert.setAlertType(Alert.AlertType.CONFIRMATION); alert.getButtonTypes().clear(); - map.sequencedKeySet().forEach(buttonType -> alert.getButtonTypes().add(buttonType)); + map.sequencedKeySet() + .forEach(buttonType -> alert.getButtonTypes().add(buttonType)); }) .map(map::get) .orElse(FileConflictChoice.CANCEL); @@ -86,4 +82,12 @@ public class BrowserAlerts { } return names; } + + public enum FileConflictChoice { + CANCEL, + SKIP, + SKIP_ALL, + REPLACE, + REPLACE_ALL + } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java index c58e76c27..97203d413 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java @@ -49,20 +49,32 @@ final class BrowserBookmarkComp extends SimpleComp { var filterText = new SimpleStringProperty(); var open = PlatformThread.sync(model.getSelected()); Predicate applicable = storeEntryWrapper -> { - return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore || - storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore) && storeEntryWrapper.getEntry().getValidity().isUsable(); + return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore + || storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore) + && storeEntryWrapper.getEntry().getValidity().isUsable(); }; - var selectedCategory = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue()); + var selectedCategory = new SimpleObjectProperty<>( + StoreViewState.get().getActiveCategory().getValue()); var section = StoreSectionMiniComp.createList( - StoreSection.createTopLevel(StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory), (s, comp) -> { + StoreSection.createTopLevel( + StoreViewState.get().getAllEntries(), storeEntryWrapper -> true, filterText, selectedCategory), + (s, comp) -> { BooleanProperty busy = new SimpleBooleanProperty(false); - comp.disable(Bindings.createBooleanBinding(() -> { - return busy.get() || !applicable.test(s.getWrapper()); - }, busy)); + comp.disable(Bindings.createBooleanBinding( + () -> { + return busy.get() || !applicable.test(s.getWrapper()); + }, + busy)); comp.apply(struc -> { open.addListener((observable, oldValue, newValue) -> { - struc.get().pseudoClassStateChanged(SELECTED, - newValue != null && newValue.getEntry().get().equals(s.getWrapper().getEntry())); + struc.get() + .pseudoClassStateChanged( + SELECTED, + newValue != null + && newValue.getEntry() + .get() + .equals(s.getWrapper() + .getEntry())); }); struc.get().setOnAction(event -> { ThreadHelper.runFailableAsync(() -> { @@ -83,14 +95,21 @@ final class BrowserBookmarkComp extends SimpleComp { }); }); }); - var category = new DataStoreCategoryChoiceComp(StoreViewState.get().getAllConnectionsCategory(), StoreViewState.get().getActiveCategory(), - selectedCategory).styleClass(Styles.LEFT_PILL); - var filter = new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {}); + var category = new DataStoreCategoryChoiceComp( + StoreViewState.get().getAllConnectionsCategory(), + StoreViewState.get().getActiveCategory(), + selectedCategory) + .styleClass(Styles.LEFT_PILL); + var filter = + new FilterComp(filterText).styleClass(Styles.RIGHT_PILL).hgrow().apply(struc -> {}); - var top = new HorizontalComp(List.of(category.minWidth(Region.USE_PREF_SIZE), filter.hgrow())).styleClass("categories").apply(struc -> { - AppFont.medium(struc.get()); - struc.get().setFillHeight(true); - }).createRegion(); + var top = new HorizontalComp(List.of(category.minWidth(Region.USE_PREF_SIZE), filter.hgrow())) + .styleClass("categories") + .apply(struc -> { + AppFont.medium(struc.get()); + struc.get().setFillHeight(true); + }) + .createRegion(); var r = section.vgrow().createRegion(); var content = new VBox(top, r); content.setFillWidth(true); @@ -108,8 +127,7 @@ final class BrowserBookmarkComp extends SimpleComp { activeTask = new TimerTask() { @Override public void run() { - if (activeTask != this) { - } + if (activeTask != this) {} // Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded())); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java index fe5e8e2f8..d3a09b826 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java @@ -24,18 +24,6 @@ import java.util.stream.Collectors; public class BrowserClipboard { - @Value - public static class Instance { - UUID uuid; - FileSystem.FileEntry baseDirectory; - List entries; - - public String toClipboardString() { - return entries.stream().map(fileEntry -> "\"" + fileEntry.getPath() + "\"").collect( - Collectors.joining(ProcessControlProvider.get().getEffectiveLocalDialect().getNewLine().getNewLineString())); - } - } - public static final Property currentCopyClipboard = new SimpleObjectProperty<>(); public static Instance currentDragClipboard; @@ -53,7 +41,8 @@ public class BrowserClipboard { } List data = (List) clipboard.getData(DataFlavor.javaFileListFlavor); - var files = data.stream().map(string -> string.toPath()).toList(); + var files = + data.stream().map(string -> string.toPath()).toList(); if (files.size() == 0) { return; } @@ -121,4 +110,20 @@ public class BrowserClipboard { return null; } + + @Value + public static class Instance { + UUID uuid; + FileSystem.FileEntry baseDirectory; + List entries; + + public String toClipboardString() { + return entries.stream() + .map(fileEntry -> "\"" + fileEntry.getPath() + "\"") + .collect(Collectors.joining(ProcessControlProvider.get() + .getEffectiveLocalDialect() + .getNewLine() + .getNewLineString())); + } + } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java index 6552e97c0..94e3e130f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserComp.java @@ -59,28 +59,32 @@ public class BrowserComp extends SimpleComp { }); var bookmarksList = new BrowserBookmarkComp(model).vgrow(); - var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()).hide( - PlatformThread.sync(Bindings.createBooleanBinding(() -> { - if (model.getOpenFileSystems().size() == 0) { - return true; - } + var localDownloadStage = new BrowserTransferComp(model.getLocalTransfersStage()) + .hide(PlatformThread.sync(Bindings.createBooleanBinding( + () -> { + if (model.getOpenFileSystems().size() == 0) { + return true; + } - if (model.getMode().isChooser()) { - return true; - } + if (model.getMode().isChooser()) { + return true; + } - return false; - }, model.getOpenFileSystems(), model.getSelected()))); + return false; + }, + model.getOpenFileSystems(), + model.getSelected()))); localDownloadStage.prefHeight(200); localDownloadStage.maxHeight(200); var vertical = new VerticalComp(List.of(bookmarksList, localDownloadStage)); - var splitPane = new SideSplitPaneComp(vertical, createTabs()).withInitialWidth( - AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()).withOnDividerChange( - AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth).apply(struc -> { - struc.getLeft().setMinWidth(200); - struc.getLeft().setMaxWidth(500); - }); + var splitPane = new SideSplitPaneComp(vertical, createTabs()) + .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) + .withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth) + .apply(struc -> { + struc.getLeft().setMinWidth(200); + struc.getLeft().setMaxWidth(500); + }); var r = addBottomBar(splitPane.createRegion()); r.getStyleClass().add("browser"); // AppFont.small(r); @@ -99,12 +103,16 @@ public class BrowserComp extends SimpleComp { selected.setSpacing(10); model.getSelection().addListener((ListChangeListener) c -> { PlatformThread.runLaterIfNeeded(() -> { - selected.getChildren().setAll(c.getList().stream().map(s -> { - var field = new TextField(s.getRawFileEntry().getPath()); - field.setEditable(false); - field.setPrefWidth(500); - return field; - }).toList()); + selected.getChildren() + .setAll(c.getList().stream() + .map(s -> { + var field = + new TextField(s.getRawFileEntry().getPath()); + field.setEditable(false); + field.setPrefWidth(500); + return field; + }) + .toList()); }); }); var spacer = new Spacer(Orientation.HORIZONTAL); @@ -123,12 +131,16 @@ public class BrowserComp extends SimpleComp { } private Comp createTabs() { - var multi = new MultiContentComp(Map., ObservableValue>of(Comp.of(() -> createTabPane()), + var multi = new MultiContentComp(Map., ObservableValue>of( + Comp.of(() -> createTabPane()), BindingsHelper.persist(Bindings.isNotEmpty(model.getOpenFileSystems())), new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)), - Bindings.createBooleanBinding(() -> { - return model.getOpenFileSystems().size() == 0 && !model.getMode().isChooser(); - }, model.getOpenFileSystems()))); + Bindings.createBooleanBinding( + () -> { + return model.getOpenFileSystems().size() == 0 + && !model.getMode().isChooser(); + }, + model.getOpenFileSystems()))); return multi; } @@ -149,7 +161,8 @@ public class BrowserComp extends SimpleComp { map.put(v, t); tabs.getTabs().add(t); }); - tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue())); + tabs.getSelectionModel() + .select(model.getOpenFileSystems().indexOf(model.getSelected().getValue())); // Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually! var modifying = new SimpleBooleanProperty(); @@ -165,9 +178,9 @@ public class BrowserComp extends SimpleComp { return; } - var source = map.entrySet() - .stream() - .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(newValue)) + var source = map.entrySet().stream() + .filter(openFileSystemModelTabEntry -> + openFileSystemModelTabEntry.getValue().equals(newValue)) .findAny() .map(Map.Entry::getKey) .orElse(null); @@ -182,9 +195,9 @@ public class BrowserComp extends SimpleComp { return; } - var toSelect = map.entrySet() - .stream() - .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getKey().equals(newValue)) + var toSelect = map.entrySet().stream() + .filter(openFileSystemModelTabEntry -> + openFileSystemModelTabEntry.getKey().equals(newValue)) .findAny() .map(Map.Entry::getValue) .orElse(null); @@ -223,9 +236,9 @@ public class BrowserComp extends SimpleComp { tabs.getTabs().addListener((ListChangeListener) c -> { while (c.next()) { for (var r : c.getRemoved()) { - var source = map.entrySet() - .stream() - .filter(openFileSystemModelTabEntry -> openFileSystemModelTabEntry.getValue().equals(r)) + var source = map.entrySet().stream() + .filter(openFileSystemModelTabEntry -> + openFileSystemModelTabEntry.getValue().equals(r)) .findAny() .orElse(null); @@ -248,14 +261,22 @@ public class BrowserComp extends SimpleComp { ring.setMinSize(16, 16); ring.setPrefSize(16, 16); ring.setMaxSize(16, 16); - ring.progressProperty().bind(Bindings.createDoubleBinding(() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy()))); + ring.progressProperty() + .bind(Bindings.createDoubleBinding( + () -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy()))); - var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore()); + var image = model.getEntry() + .get() + .getProvider() + .getDisplayIconFileName(model.getEntry().getStore()); var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion(); - tab.graphicProperty().bind(Bindings.createObjectBinding(() -> { - return model.getBusy().get() ? ring : logo; - }, PlatformThread.sync(model.getBusy()))); + tab.graphicProperty() + .bind(Bindings.createObjectBinding( + () -> { + return model.getBusy().get() ? ring : logo; + }, + PlatformThread.sync(model.getBusy()))); tab.setText(model.getName()); tab.setContent(new OpenFileSystemComp(model).createSimple()); @@ -276,12 +297,17 @@ public class BrowserComp extends SimpleComp { StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container"); c.getStyleClass().add("color-box"); - var color = DataStorage.get().getRootForEntry(model.getEntry().get()).getColor(); + var color = DataStorage.get() + .getRootForEntry(model.getEntry().get()) + .getColor(); if (color != null) { c.getStyleClass().add(color.getId()); } new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c); - c.addEventHandler(DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater(() -> tabs.getSelectionModel().select(tab))); + c.addEventHandler( + DragEvent.DRAG_ENTERED, + mouseEvent -> Platform.runLater( + () -> tabs.getSelectionModel().select(tab))); }); } }); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java index 189ee190c..dfc94472f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserContextMenu.java @@ -24,6 +24,17 @@ final class BrowserContextMenu extends ContextMenu { createMenu(); } + private static List resolveIfNeeded(BrowserAction action, List selected) { + return action.automaticallyResolveLinks() + ? selected.stream() + .map(browserEntry -> new BrowserEntry( + browserEntry.getRawFileEntry().resolved(), + browserEntry.getModel(), + browserEntry.isSynthetic())) + .toList() + : selected; + } + private void createMenu() { AppFont.normal(this.getStyleableNode()); @@ -81,7 +92,10 @@ final class BrowserContextMenu extends ContextMenu { } m.setDisable(!a.isActive(model, used)); - if (la.getProFeatureId() != null && !LicenseProvider.get().getFeature(la.getProFeatureId()).isSupported()) { + if (la.getProFeatureId() != null + && !LicenseProvider.get() + .getFeature(la.getProFeatureId()) + .isSupported()) { m.setDisable(true); m.setGraphic(new FontIcon("mdi2p-professional-hexagon")); } @@ -91,15 +105,4 @@ final class BrowserContextMenu extends ContextMenu { } } } - - private static List resolveIfNeeded(BrowserAction action, List selected) { - return action.automaticallyResolveLinks() - ? selected.stream() - .map(browserEntry -> new BrowserEntry( - browserEntry.getRawFileEntry().resolved(), - browserEntry.getModel(), - browserEntry.isSynthetic())) - .toList() - : selected; - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java index 5a1a317e7..093018be4 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListComp.java @@ -81,18 +81,18 @@ final class BrowserFileListComp extends SimpleComp { filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing())); var sizeCol = new TableColumn("Size"); - sizeCol.setCellValueFactory(param -> - new SimpleLongProperty(param.getValue().getRawFileEntry().resolved().getSize())); + sizeCol.setCellValueFactory(param -> new SimpleLongProperty( + param.getValue().getRawFileEntry().resolved().getSize())); sizeCol.setCellFactory(col -> new FileSizeCell()); var mtimeCol = new TableColumn("Modified"); - mtimeCol.setCellValueFactory(param -> - new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getDate())); + mtimeCol.setCellValueFactory(param -> new SimpleObjectProperty<>( + param.getValue().getRawFileEntry().resolved().getDate())); mtimeCol.setCellFactory(col -> new FileTimeCell()); var modeCol = new TableColumn("Attributes"); - modeCol.setCellValueFactory(param -> - new SimpleObjectProperty<>(param.getValue().getRawFileEntry().resolved().getMode())); + modeCol.setCellValueFactory(param -> new SimpleObjectProperty<>( + param.getValue().getRawFileEntry().resolved().getMode())); modeCol.setCellFactory(col -> new FileModeCell()); modeCol.setSortable(false); @@ -247,12 +247,20 @@ final class BrowserFileListComp extends SimpleComp { } if (row.getItem() != null - && row.getItem().getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) { + && row.getItem() + .getRawFileEntry() + .resolved() + .getKind() + == FileKind.DIRECTORY) { return event.getButton() == MouseButton.SECONDARY; } if (row.getItem() != null - && row.getItem().getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY) { + && row.getItem() + .getRawFileEntry() + .resolved() + .getKind() + != FileKind.DIRECTORY) { return event.getButton() == MouseButton.SECONDARY || event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2; } @@ -424,6 +432,54 @@ final class BrowserFileListComp extends SimpleComp { } } + private static class FileSizeCell extends TableCell { + + @Override + protected void updateItem(Number fileSize, boolean empty) { + super.updateItem(fileSize, empty); + if (empty || getTableRow() == null || getTableRow().getItem() == null) { + setText(null); + } else { + var path = getTableRow().getItem(); + if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) { + setText(""); + } else { + setText(byteCount(fileSize.longValue())); + } + } + } + } + + private static class FileModeCell extends TableCell { + + @Override + protected void updateItem(String mode, boolean empty) { + super.updateItem(mode, empty); + if (empty || getTableRow() == null || getTableRow().getItem() == null) { + setText(null); + } else { + setText(mode); + } + } + } + + private static class FileTimeCell extends TableCell { + + @Override + protected void updateItem(Instant fileTime, boolean empty) { + super.updateItem(fileTime, empty); + if (empty) { + setText(null); + } else { + setText( + fileTime != null + ? HumanReadableFormat.date( + fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime()) + : ""); + } + } + } + private class FilenameCell extends TableCell { private final StringProperty img = new SimpleStringProperty(); @@ -518,52 +574,4 @@ final class BrowserFileListComp extends SimpleComp { } } } - - private static class FileSizeCell extends TableCell { - - @Override - protected void updateItem(Number fileSize, boolean empty) { - super.updateItem(fileSize, empty); - if (empty || getTableRow() == null || getTableRow().getItem() == null) { - setText(null); - } else { - var path = getTableRow().getItem(); - if (path.getRawFileEntry().resolved().getKind() == FileKind.DIRECTORY) { - setText(""); - } else { - setText(byteCount(fileSize.longValue())); - } - } - } - } - - private static class FileModeCell extends TableCell { - - @Override - protected void updateItem(String mode, boolean empty) { - super.updateItem(mode, empty); - if (empty || getTableRow() == null || getTableRow().getItem() == null) { - setText(null); - } else { - setText(mode); - } - } - } - - private static class FileTimeCell extends TableCell { - - @Override - protected void updateItem(Instant fileTime, boolean empty) { - super.updateItem(fileTime, empty); - if (empty) { - setText(null); - } else { - setText( - fileTime != null - ? HumanReadableFormat.date( - fileTime.atZone(ZoneId.systemDefault()).toLocalDateTime()) - : ""); - } - } - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java index 6a7b8c8aa..3a04eb42a 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListCompEntry.java @@ -27,7 +27,8 @@ public class BrowserFileListCompEntry { private Point2D lastOver = new Point2D(-1, -1); private TimerTask activeTask; - public BrowserFileListCompEntry(TableView tv, Node row, BrowserEntry item, BrowserFileListModel model) { + public BrowserFileListCompEntry( + TableView tv, Node row, BrowserEntry item, BrowserFileListModel model) { this.tv = tv; this.row = row; this.item = item; @@ -59,14 +60,18 @@ public class BrowserFileListCompEntry { var all = tv.getItems(); var index = item != null ? all.indexOf(item) : all.size() - 1; - var min = Math.min(index, tv.getSelectionModel().getSelectedIndices().stream() - .mapToInt(value -> value) - .min() - .orElse(1)); - var max = Math.max(index, tv.getSelectionModel().getSelectedIndices().stream() - .mapToInt(value -> value) - .max() - .orElse(all.indexOf(item))); + var min = Math.min( + index, + tv.getSelectionModel().getSelectedIndices().stream() + .mapToInt(value -> value) + .min() + .orElse(1)); + var max = Math.max( + index, + tv.getSelectionModel().getSelectedIndices().stream() + .mapToInt(value -> value) + .max() + .orElse(all.indexOf(item))); var toSelect = new ArrayList(); for (int i = min; i <= max; i++) { @@ -98,13 +103,15 @@ public class BrowserFileListCompEntry { return false; } - if (!Objects.equals(model.getFileSystemModel().getFileSystem(), cb.getEntries().getFirst().getFileSystem())) { + if (!Objects.equals( + model.getFileSystemModel().getFileSystem(), + cb.getEntries().getFirst().getFileSystem())) { return true; } // Prevent drag and drops of files into the current directory - if (cb.getBaseDirectory() != null && cb - .getBaseDirectory() + if (cb.getBaseDirectory() != null + && cb.getBaseDirectory() .getPath() .equals(model.getFileSystemModel().getCurrentDirectory().getPath()) && (item == null || item.getRawFileEntry().getKind() != FileKind.DIRECTORY)) { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java index 30a7bd3b8..aab3addad 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileListModel.java @@ -2,8 +2,8 @@ package io.xpipe.app.browser; import io.xpipe.app.fxcomps.util.BindingsHelper; import io.xpipe.app.issue.ErrorEvent; -import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileSystem; import javafx.beans.property.Property; import javafx.beans.property.SimpleBooleanProperty; @@ -99,8 +99,9 @@ public final class BrowserFileListModel { path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); var comp = comparatorProperty.getValue(); - Comparator us = - comp != null ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) : syntheticFirst.thenComparing(dirsFirst); + Comparator us = comp != null + ? syntheticFirst.thenComparing(dirsFirst).thenComparing(comp) + : syntheticFirst.thenComparing(dirsFirst); l.sort(us); } @@ -110,14 +111,17 @@ public final class BrowserFileListModel { boolean exists; try { - exists = fileSystemModel.getFileSystem().fileExists(newFullPath) || fileSystemModel.getFileSystem().directoryExists(newFullPath); + exists = fileSystemModel.getFileSystem().fileExists(newFullPath) + || fileSystemModel.getFileSystem().directoryExists(newFullPath); } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); return false; } if (exists) { - ErrorEvent.fromMessage("Target " + newFullPath + " does already exist").expected().handle(); + ErrorEvent.fromMessage("Target " + newFullPath + " does already exist") + .expected() + .handle(); fileSystemModel.refresh(); return false; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java index f9c731b34..f1846e14e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java @@ -16,6 +16,14 @@ import org.kordamp.ikonli.javafx.FontIcon; public class BrowserFilterComp extends Comp { + private final OpenFileSystemModel model; + private final Property filterString; + + public BrowserFilterComp(OpenFileSystemModel model, Property filterString) { + this.model = model; + this.filterString = filterString; + } + @Override public Structure createBase() { var expanded = new SimpleBooleanProperty(); @@ -98,12 +106,4 @@ public class BrowserFilterComp extends Comp { return box; } } - - private final OpenFileSystemModel model; - private final Property filterString; - - public BrowserFilterComp(OpenFileSystemModel model, Property filterString) { - this.model = model; - this.filterString = filterString; - } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java index 52d64ae22..5efd5d464 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserModel.java @@ -33,6 +33,7 @@ public class BrowserModel { private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); private final ObservableList selection = FXCollections.observableArrayList(); private final BrowserSavedState savedState; + @Setter private Consumer> onFinish; @@ -101,8 +102,10 @@ public class BrowserModel { return; } - var stores = chosen.stream().map( - entry -> new FileReference(selected.getValue().getEntry(), entry.getRawFileEntry().getPath())).toList(); + var stores = chosen.stream() + .map(entry -> new FileReference( + selected.getValue().getEntry(), entry.getRawFileEntry().getPath())) + .toList(); onFinish.accept(stores); } @@ -113,8 +116,11 @@ public class BrowserModel { } private void closeFileSystemSync(OpenFileSystemModel open) { - if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) && savedState != null && open.getCurrentPath().get() != null) { - savedState.add(new BrowserSavedState.Entry(open.getEntry().get().getUuid(), open.getCurrentPath().get())); + if (DataStorage.get().getStoreEntries().contains(open.getEntry().get()) + && savedState != null + && open.getCurrentPath().get() != null) { + savedState.add(new BrowserSavedState.Entry( + open.getEntry().get().getUuid(), open.getCurrentPath().get())); } open.closeSync(); synchronized (BrowserModel.this) { @@ -122,7 +128,10 @@ public class BrowserModel { } } - public void openFileSystemAsync(DataStoreEntryRef store, FailableFunction path, BooleanProperty externalBusy) { + public void openFileSystemAsync( + DataStoreEntryRef store, + FailableFunction path, + BooleanProperty externalBusy) { if (store == null) { return; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java index 2849892c6..b39e0f0fc 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java @@ -106,7 +106,6 @@ public class BrowserNavBar extends SimpleComp { }) .augment(new SimpleCompStructure<>(homeButton)); - var historyButton = new Button(null, new FontIcon("mdi2h-history")); historyButton.setAccessibleText("History"); historyButton.getStyleClass().add(Styles.RIGHT_PILL); @@ -146,7 +145,6 @@ public class BrowserNavBar extends SimpleComp { .maxHeightProperty() .bind(((Region) struc.get().getChildren().get(1)).heightProperty()); - ((Region) struc.get().getChildren().get(2)) .minHeightProperty() .bind(((Region) struc.get().getChildren().get(1)).heightProperty()); @@ -197,7 +195,8 @@ public class BrowserNavBar extends SimpleComp { cm.getItems().add(current); } - var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream().toList(); + var b = model.getHistory().getBackwardHistory(Integer.MAX_VALUE).stream() + .toList(); if (!b.isEmpty()) { cm.getItems().add(new SeparatorMenuItem()); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java index f96e9999d..55d498c79 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java @@ -20,12 +20,6 @@ import java.util.List; @JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class) public class BrowserSavedStateImpl implements BrowserSavedState { - static BrowserSavedStateImpl load() { - return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> { - return new BrowserSavedStateImpl(FXCollections.observableArrayList()); - }); - } - @JsonSerialize(as = List.class) ObservableList lastSystems; @@ -33,25 +27,10 @@ public class BrowserSavedStateImpl implements BrowserSavedState { this.lastSystems = FXCollections.observableArrayList(lastSystems); } - public static class Deserializer extends StdDeserializer { - - protected Deserializer() { - super(BrowserSavedStateImpl.class); - } - - @Override - @SneakyThrows - public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) { - var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p); - JavaType javaType = JacksonMapper.getDefault() - .getTypeFactory() - .constructCollectionLikeType(List.class, Entry.class); - List ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType); - if (ls == null) { - ls = List.of(); - } - return new BrowserSavedStateImpl(ls); - } + static BrowserSavedStateImpl load() { + return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> { + return new BrowserSavedStateImpl(FXCollections.observableArrayList()); + }); } @Override @@ -72,4 +51,24 @@ public class BrowserSavedStateImpl implements BrowserSavedState { public ObservableList getEntries() { return lastSystems; } + + public static class Deserializer extends StdDeserializer { + + protected Deserializer() { + super(BrowserSavedStateImpl.class); + } + + @Override + @SneakyThrows + public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) { + var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p); + JavaType javaType = + JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(List.class, Entry.class); + List ls = JacksonMapper.getDefault().treeToValue(tree.remove("lastSystems"), javaType); + if (ls == null) { + ls = List.of(); + } + return new BrowserSavedStateImpl(ls); + } + } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java index 55f242b94..40eba748e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java @@ -31,6 +31,12 @@ import java.util.function.Function; @AllArgsConstructor public class BrowserSelectionListComp extends SimpleComp { + ObservableList list; + Function> nameTransformation; + public BrowserSelectionListComp(ObservableList list) { + this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath()))); + } + public static Image snapshot(ObservableList list) { var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion(); var scene = new Scene(r); @@ -41,13 +47,6 @@ public class BrowserSelectionListComp extends SimpleComp { return r.snapshot(parameters, null); } - ObservableList list; - Function> nameTransformation; - - public BrowserSelectionListComp(ObservableList list) { - this(list, entry -> new SimpleStringProperty(FileNames.getFileName(entry.getPath()))); - } - @Override protected Region createSimple() { var c = new ListBoxViewComp<>(list, list, entry -> { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java index 84657a451..5f999df35 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java @@ -24,7 +24,12 @@ public class BrowserStatusBarComp extends SimpleComp { @Override protected Region createSimple() { var bar = new ToolBar(); - bar.getItems().setAll(createClipboardStatus().createRegion(), createProgressStatus().createRegion(), new Spacer(), createSelectionStatus().createRegion()); + bar.getItems() + .setAll( + createClipboardStatus().createRegion(), + createProgressStatus().createRegion(), + new Spacer(), + createSelectionStatus().createRegion()); bar.getStyleClass().add("status-bar"); bar.setOnDragDetected(event -> { event.consume(); @@ -40,20 +45,26 @@ public class BrowserStatusBarComp extends SimpleComp { private Comp createProgressStatus() { var transferredCount = PlatformThread.sync(Bindings.createStringBinding( () -> { - return HumanReadableFormat.byteCount(model.getProgress().getValue().getTransferred()); + return HumanReadableFormat.byteCount( + model.getProgress().getValue().getTransferred()); }, model.getProgress())); var allCount = PlatformThread.sync(Bindings.createStringBinding( () -> { - return HumanReadableFormat.byteCount(model.getProgress().getValue().getTotal()); + return HumanReadableFormat.byteCount( + model.getProgress().getValue().getTotal()); }, model.getProgress())); var progressComp = new LabelComp(Bindings.createStringBinding( () -> { - if (model.getProgress().getValue() == null || model.getProgress().getValue().done()) { + if (model.getProgress().getValue() == null + || model.getProgress().getValue().done()) { return null; } else { - return (model.getProgress().getValue().getName() != null ? model.getProgress().getValue().getName() + " " : "") + transferredCount.getValue() + " / " + allCount.getValue(); + return (model.getProgress().getValue().getName() != null + ? model.getProgress().getValue().getName() + " " + : "") + + transferredCount.getValue() + " / " + allCount.getValue(); } }, transferredCount, diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java index 5d2d62daa..7e1a14ec5 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java @@ -44,20 +44,33 @@ public class BrowserTransferComp extends SimpleComp { new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); var binding = BindingsHelper.mappedContentBinding(model.getItems(), item -> item.getFileEntry()); - var list = new BrowserSelectionListComp(binding, entry -> Bindings.createStringBinding(() -> { - var sourceItem = model.getItems().stream().filter(item -> item.getFileEntry() == entry).findAny(); - if (sourceItem.isEmpty()) { - return "?"; - } - var name = sourceItem.get().downloadFinished().get() ? "Local" : DataStorage.get().getStoreDisplayName(entry.getFileSystem().getStore()).orElse("?"); - return FileNames.getFileName(entry.getPath()) + " (" + name + ")"; - }, model.getAllDownloaded())) + var list = new BrowserSelectionListComp( + binding, + entry -> Bindings.createStringBinding( + () -> { + var sourceItem = model.getItems().stream() + .filter(item -> item.getFileEntry() == entry) + .findAny(); + if (sourceItem.isEmpty()) { + return "?"; + } + var name = + sourceItem.get().downloadFinished().get() + ? "Local" + : DataStorage.get() + .getStoreDisplayName(entry.getFileSystem() + .getStore()) + .orElse("?"); + return FileNames.getFileName(entry.getPath()) + " (" + name + ")"; + }, + model.getAllDownloaded())) .apply(struc -> struc.get().setMinHeight(150)) .grow(false, true); - var dragNotice = new LabelComp(model.getAllDownloaded().flatMap(aBoolean -> aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles"))) + var dragNotice = new LabelComp(model.getAllDownloaded() + .flatMap(aBoolean -> + aBoolean ? AppI18n.observable("dragLocalFiles") : AppI18n.observable("dragFiles"))) .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2e-export"))) - .hide(PlatformThread.sync( - BindingsHelper.persist(Bindings.isEmpty(model.getItems())))) + .hide(PlatformThread.sync(BindingsHelper.persist(Bindings.isEmpty(model.getItems())))) .grow(true, false) .apply(struc -> struc.get().setPadding(new Insets(8))); @@ -95,7 +108,8 @@ public class BrowserTransferComp extends SimpleComp { } // Accept drops from outside the app window - if (event.getGestureSource() == null && !event.getDragboard().getFiles().isEmpty()) { + if (event.getGestureSource() == null + && !event.getDragboard().getFiles().isEmpty()) { event.acceptTransferModes(TransferMode.ANY); event.consume(); } @@ -109,7 +123,11 @@ public class BrowserTransferComp extends SimpleComp { } var files = drag.getEntries(); - model.drop(model.getBrowserModel().getSelected().getValue(), files); + model.drop( + model.getBrowserModel() + .getSelected() + .getValue(), + files); event.setDropCompleted(true); event.consume(); } @@ -126,7 +144,9 @@ public class BrowserTransferComp extends SimpleComp { return; } - var selected = model.getItems().stream().map(BrowserTransferModel.Item::getFileEntry).toList(); + var selected = model.getItems().stream() + .map(BrowserTransferModel.Item::getFileEntry) + .toList(); Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY); var cc = BrowserClipboard.startDrag(null, selected); @@ -143,9 +163,8 @@ public class BrowserTransferComp extends SimpleComp { return Optional.empty(); } - return Optional.of(file - .toRealPath() - .toFile()); + return Optional.of( + file.toRealPath().toFile()); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java index e71b1cc33..76cb87a74 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java @@ -36,30 +36,6 @@ public class BrowserTransferModel { t.setName("file downloader"); return t; }); - - @Value - public static class Item { - OpenFileSystemModel openFileSystemModel; - String name; - FileSystem.FileEntry fileEntry; - Path localFile; - Property progress; - - public Item(OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) { - this.openFileSystemModel = openFileSystemModel; - this.name = name; - this.fileEntry = fileEntry; - this.localFile = localFile; - this.progress = new SimpleObjectProperty<>(BrowserTransferProgress.empty(fileEntry.getName(), fileEntry.getSize())); - } - - public ObservableBooleanValue downloadFinished() { - return Bindings.createBooleanBinding(() -> { - return progress.getValue().done(); - }, progress); - } - } - BrowserModel browserModel; ObservableList items = FXCollections.observableArrayList(); BooleanProperty downloading = new SimpleBooleanProperty(); @@ -132,17 +108,15 @@ public class BrowserTransferModel { continue; } - if (item.getOpenFileSystemModel() != null && item.getOpenFileSystemModel().isClosed()) { + if (item.getOpenFileSystemModel() != null + && item.getOpenFileSystemModel().isClosed()) { continue; } try { try (var b = new BooleanScope(downloading).start()) { FileSystemHelper.dropFilesInto( - FileSystemHelper.getLocal(TEMP), - List.of(item.getFileEntry()), - true, - progress -> { + FileSystemHelper.getLocal(TEMP), List.of(item.getFileEntry()), true, progress -> { item.getProgress().setValue(progress); item.getOpenFileSystemModel().getProgress().setValue(progress); }); @@ -155,4 +129,31 @@ public class BrowserTransferModel { allDownloaded.set(true); }); } + + @Value + public static class Item { + OpenFileSystemModel openFileSystemModel; + String name; + FileSystem.FileEntry fileEntry; + Path localFile; + Property progress; + + public Item( + OpenFileSystemModel openFileSystemModel, String name, FileSystem.FileEntry fileEntry, Path localFile) { + this.openFileSystemModel = openFileSystemModel; + this.name = name; + this.fileEntry = fileEntry; + this.localFile = localFile; + this.progress = + new SimpleObjectProperty<>(BrowserTransferProgress.empty(fileEntry.getName(), fileEntry.getSize())); + } + + public ObservableBooleanValue downloadFinished() { + return Bindings.createBooleanBinding( + () -> { + return progress.getValue().done(); + }, + progress); + } + } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java index d9907c3ea..6dc36aed6 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java @@ -5,6 +5,10 @@ import lombok.Value; @Value public class BrowserTransferProgress { + String name; + long transferred; + long total; + static BrowserTransferProgress empty() { return new BrowserTransferProgress(null, 0, 0); } @@ -17,10 +21,6 @@ public class BrowserTransferProgress { return new BrowserTransferProgress(name, size, size); } - String name; - long transferred; - long total; - public boolean done() { return transferred >= total; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java index 57a51123e..a08f0fe55 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java @@ -42,7 +42,9 @@ public class BrowserWelcomeComp extends SimpleComp { var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL)); vbox.setAlignment(Pos.CENTER_LEFT); - var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75).padding(new Insets(5, 0, 0, 0)).createRegion(); + var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Hips.svg"), 50, 75) + .padding(new Insets(5, 0, 0, 0)) + .createRegion(); var hbox = new HBox(img, vbox); hbox.setAlignment(Pos.CENTER_LEFT); hbox.setSpacing(15); @@ -68,10 +70,14 @@ public class BrowserWelcomeComp extends SimpleComp { }); var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list); - var header = new LabelComp(Bindings.createStringBinding(() -> { - return !empty.get() ? "You were recently connected to the following systems:" : - "Here you will be able to see where you left off last time."; - }, empty)).createRegion(); + var header = new LabelComp(Bindings.createStringBinding( + () -> { + return !empty.get() + ? "You were recently connected to the following systems:" + : "Here you will be able to see where you left off last time."; + }, + empty)) + .createRegion(); AppFont.setSize(header, 1); vbox.getChildren().add(header); @@ -79,22 +85,32 @@ public class BrowserWelcomeComp extends SimpleComp { storeList.setSpacing(8); var listBox = new ListBoxViewComp<>(list, list, e -> { - var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); - var graphic = entry.get().getProvider().getDisplayIconFileName(entry.get().getStore()); - var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40); - view.padding(new Insets(2, 8, 2, 8)); - var content = - JfxHelper.createNamedEntry(DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic); - var disable = new SimpleBooleanProperty(); - return new ButtonComp(null, content, () -> { - ThreadHelper.runAsync(() -> { - model.restoreStateAsync(e, disable); - }); - }).accessibleText(DataStorage.get().getStoreDisplayName(entry.get())).disable(disable).styleClass("color-listBox").apply(struc -> struc.get().setMaxWidth(2000)).grow(true, false); - }).apply(struc -> { - VBox vBox = (VBox) struc.get().getContent(); - vBox.setSpacing(10); - }).hide(empty).createRegion(); + var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); + var graphic = entry.get() + .getProvider() + .getDisplayIconFileName(entry.get().getStore()); + var view = PrettyImageHelper.ofFixedSize(graphic, 50, 40); + view.padding(new Insets(2, 8, 2, 8)); + var content = JfxHelper.createNamedEntry( + DataStorage.get().getStoreDisplayName(entry.get()), e.getPath(), graphic); + var disable = new SimpleBooleanProperty(); + return new ButtonComp(null, content, () -> { + ThreadHelper.runAsync(() -> { + model.restoreStateAsync(e, disable); + }); + }) + .accessibleText(DataStorage.get().getStoreDisplayName(entry.get())) + .disable(disable) + .styleClass("color-listBox") + .apply(struc -> struc.get().setMaxWidth(2000)) + .grow(true, false); + }) + .apply(struc -> { + VBox vBox = (VBox) struc.get().getContent(); + vBox.setSpacing(10); + }) + .hide(empty) + .createRegion(); var layout = new VBox(); layout.getStyleClass().add("welcome"); @@ -107,9 +123,12 @@ public class BrowserWelcomeComp extends SimpleComp { layout.getChildren().add(Comp.separator().hide(empty).createRegion()); var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> { - model.restoreState(state); - actionEvent.consume(); - }).grow(true, false).hide(empty).accessibleTextKey("restoreAllSessions"); + model.restoreState(state); + actionEvent.consume(); + }) + .grow(true, false) + .hide(empty) + .accessibleTextKey("restoreAllSessions"); layout.getChildren().add(tile.createRegion()); return layout; diff --git a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java index 9f63ae1ec..d710fcce3 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/FileSystemHelper.java @@ -20,6 +20,9 @@ import java.util.function.Consumer; public class FileSystemHelper { + private static final int DEFAULT_BUFFER_SIZE = 16384; + private static FileSystem localFileSystem; + public static String adjustPath(OpenFileSystemModel model, String path) { if (path == null) { return null; @@ -120,7 +123,8 @@ public class FileSystemHelper { } if (!model.getFileSystem().directoryExists(path)) { - throw ErrorEvent.unreportable(new IllegalArgumentException(String.format("Directory %s does not exist", path))); + throw ErrorEvent.unreportable( + new IllegalArgumentException(String.format("Directory %s does not exist", path))); } try { @@ -131,8 +135,6 @@ public class FileSystemHelper { } } - private static FileSystem localFileSystem; - public static FileSystem.FileEntry getLocal(Path file) throws Exception { if (localFileSystem == null) { localFileSystem = new LocalStore().createFileSystem(); @@ -150,7 +152,8 @@ public class FileSystemHelper { Files.isDirectory(file) ? FileKind.DIRECTORY : FileKind.FILE); } - public static void dropLocalFilesInto(FileSystem.FileEntry entry, List files, Consumer progress) throws Exception { + public static void dropLocalFilesInto( + FileSystem.FileEntry entry, List files, Consumer progress) throws Exception { var entries = files.stream() .map(path -> { try { @@ -178,8 +181,11 @@ public class FileSystemHelper { } public static void dropFilesInto( - FileSystem.FileEntry target, List files, boolean explicitCopy, Consumer progress - ) throws Exception { + FileSystem.FileEntry target, + List files, + boolean explicitCopy, + Consumer progress) + throws Exception { if (files.isEmpty()) { progress.accept(BrowserTransferProgress.empty()); return; @@ -204,7 +210,12 @@ public class FileSystemHelper { } private static void dropFileAcrossSameFileSystem( - FileSystem.FileEntry target, FileSystem.FileEntry source, boolean explicitCopy, AtomicReference lastConflictChoice, boolean multiple) throws Exception { + FileSystem.FileEntry target, + FileSystem.FileEntry source, + boolean explicitCopy, + AtomicReference lastConflictChoice, + boolean multiple) + throws Exception { // Prevent dropping directory into itself if (source.getPath().equals(target.getPath())) { return; @@ -218,7 +229,8 @@ public class FileSystemHelper { } if (source.getKind() == FileKind.DIRECTORY && target.getFileSystem().directoryExists(targetFile)) { - throw ErrorEvent.unreportable(new IllegalArgumentException("Target directory " + targetFile + " does already exist")); + throw ErrorEvent.unreportable( + new IllegalArgumentException("Target directory " + targetFile + " does already exist")); } if (!handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple)) { @@ -232,7 +244,12 @@ public class FileSystemHelper { } } - private static void dropFileAcrossFileSystems(FileSystem.FileEntry target, FileSystem.FileEntry source, Consumer progress, AtomicReference lastConflictChoice, boolean multiple) + private static void dropFileAcrossFileSystems( + FileSystem.FileEntry target, + FileSystem.FileEntry source, + Consumer progress, + AtomicReference lastConflictChoice, + boolean multiple) throws Exception { if (target.getKind() != FileKind.DIRECTORY) { throw new IllegalStateException("Target " + target.getPath() + " is not a directory"); @@ -273,7 +290,8 @@ public class FileSystemHelper { if (sourceFile.getKind() == FileKind.DIRECTORY) { target.getFileSystem().mkdirs(targetFile); } else if (sourceFile.getKind() == FileKind.FILE) { - if (!handleChoice(lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) { + if (!handleChoice( + lastConflictChoice, target.getFileSystem(), targetFile, multiple || flatFiles.size() > 1)) { continue; } @@ -282,7 +300,7 @@ public class FileSystemHelper { try { inputStream = sourceFile.getFileSystem().openInput(sourceFile.getPath()); outputStream = target.getFileSystem().openOutput(targetFile, source.getSize()); - transfer(source,inputStream, outputStream,transferred, totalSize, progress); + transfer(source, inputStream, outputStream, transferred, totalSize, progress); inputStream.transferTo(OutputStream.nullOutputStream()); } catch (Exception ex) { if (inputStream != null) { @@ -325,7 +343,12 @@ public class FileSystemHelper { progress.accept(BrowserTransferProgress.finished(source.getName(), totalSize.get())); } - private static boolean handleChoice(AtomicReference previous, FileSystem fileSystem, String target, boolean multiple) throws Exception { + private static boolean handleChoice( + AtomicReference previous, + FileSystem fileSystem, + String target, + boolean multiple) + throws Exception { if (previous.get() == BrowserAlerts.FileConflictChoice.CANCEL) { return false; } @@ -339,7 +362,6 @@ public class FileSystemHelper { return false; } - var choice = BrowserAlerts.showFileConflictAlert(target, multiple); if (choice == BrowserAlerts.FileConflictChoice.CANCEL) { previous.set(BrowserAlerts.FileConflictChoice.CANCEL); @@ -363,9 +385,14 @@ public class FileSystemHelper { return true; } - private static final int DEFAULT_BUFFER_SIZE = 16384; - - private static void transfer(FileSystem.FileEntry source, InputStream inputStream, OutputStream outputStream, AtomicLong transferred, AtomicLong total, Consumer progress) throws IOException { + private static void transfer( + FileSystem.FileEntry source, + InputStream inputStream, + OutputStream outputStream, + AtomicLong transferred, + AtomicLong total, + Consumer progress) + throws IOException { var bs = (int) Math.min(DEFAULT_BUFFER_SIZE, source.getSize()); byte[] buffer = new byte[bs]; int read; diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java index ba9f15c86..6ec8f60fa 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java @@ -8,7 +8,10 @@ import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.process.*; +import io.xpipe.core.process.ProcessControlProvider; +import io.xpipe.core.process.ShellControl; +import io.xpipe.core.process.ShellDialects; +import io.xpipe.core.process.ShellOpenFunction; import io.xpipe.core.store.*; import io.xpipe.core.util.FailableConsumer; import javafx.beans.binding.Bindings; @@ -27,21 +30,22 @@ import java.util.stream.Stream; public final class OpenFileSystemModel { private final DataStoreEntryRef entry; - private FileSystem fileSystem; private final Property filter = new SimpleStringProperty(); private final BrowserFileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); private final OpenFileSystemHistory history = new OpenFileSystemHistory(); private final BooleanProperty busy = new SimpleBooleanProperty(); private final BrowserModel browserModel; - private OpenFileSystemSavedState savedState; - private OpenFileSystemCache cache; private final Property overlay = new SimpleObjectProperty<>(); private final BooleanProperty inOverview = new SimpleBooleanProperty(); private final String name; private final String tooltip; + private final Property progress = + new SimpleObjectProperty<>(BrowserTransferProgress.empty()); + private FileSystem fileSystem; + private OpenFileSystemSavedState savedState; + private OpenFileSystemCache cache; private int customScriptsStartIndex; - private final Property progress = new SimpleObjectProperty<>(BrowserTransferProgress.empty()); public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef entry) { this.browserModel = browserModel; @@ -57,7 +61,10 @@ public final class OpenFileSystemModel { } public boolean isBusy() { - return !progress.getValue().done() || (fileSystem != null && fileSystem.getShell().isPresent() && fileSystem.getShell().get().getLock().isLocked()); + return !progress.getValue().done() + || (fileSystem != null + && fileSystem.getShell().isPresent() + && fileSystem.getShell().get().getLock().isLocked()); } private void startIfNeeded() { @@ -171,15 +178,13 @@ public final class OpenFileSystemModel { var directory = currentPath.get(); var name = adjustedPath + " - " + entry.get().getName(); ThreadHelper.runFailableAsync(() -> { - if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) { + if (ShellDialects.getStartableDialects().stream() + .anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand(null)))) { TerminalLauncher.open( entry.getEntry(), name, directory, - fileSystem - .getShell() - .get() - .singularSubShell(ShellOpenFunction.of(adjustedPath))); + fileSystem.getShell().get().singularSubShell(ShellOpenFunction.of(adjustedPath))); } else { TerminalLauncher.open( entry.getEntry(), @@ -303,7 +308,8 @@ public final class OpenFileSystemModel { startIfNeeded(); var abs = FileNames.join(getCurrentDirectory().getPath(), name); if (fileSystem.directoryExists(abs)) { - throw ErrorEvent.unreportable(new IllegalStateException(String.format("Directory %s already exists", abs))); + throw ErrorEvent.unreportable( + new IllegalStateException(String.format("Directory %s already exists", abs))); } fileSystem.mkdirs(abs); @@ -378,7 +384,8 @@ public final class OpenFileSystemModel { BooleanScope.execute(busy, () -> { var fs = entry.getStore().createFileSystem(); if (fs.getShell().isPresent()) { - this.customScriptsStartIndex = fs.getShell().get().getInitCommands().size(); + this.customScriptsStartIndex = + fs.getShell().get().getInitCommands().size(); ProcessControlProvider.get().withDefaultScripts(fs.getShell().get()); } fs.open(); @@ -413,7 +420,8 @@ public final class OpenFileSystemModel { BooleanScope.execute(busy, () -> { if (fileSystem.getShell().isPresent()) { var connection = fileSystem.getShell().get(); - var name = (directory != null ? directory + " - " : "") + entry.get().getName(); + var name = (directory != null ? directory + " - " : "") + + entry.get().getName(); TerminalLauncher.open(entry.getEntry(), name, directory, connection); // Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java index 588553d02..ca21e618b 100644 --- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemSavedState.java @@ -33,6 +33,83 @@ import java.util.stream.Collectors; @JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class) public class OpenFileSystemSavedState { + private static final Timer TIMEOUT_TIMER = new Timer(true); + private static final int STORED = 10; + @Setter + private OpenFileSystemModel model; + private String lastDirectory; + @NonNull + private ObservableList recentDirectories; + + public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList recentDirectories) { + this.lastDirectory = lastDirectory; + this.recentDirectories = recentDirectories; + } + + public OpenFileSystemSavedState() { + lastDirectory = null; + recentDirectories = FXCollections.observableList(new ArrayList<>(STORED)); + } + + static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) { + var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> { + return new OpenFileSystemSavedState(); + }); + state.setModel(model); + return state; + } + + public void save() { + if (model == null) { + return; + } + + AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this); + } + + public void cd(String dir) { + if (dir == null) { + lastDirectory = null; + return; + } + + lastDirectory = dir; + // After 10 seconds + TIMEOUT_TIMER.schedule( + new TimerTask() { + @Override + public void run() { + // Synchronize with platform thread + Platform.runLater(() -> { + if (model.isClosed()) { + return; + } + + if (Objects.equals(lastDirectory, dir)) { + updateRecent(dir); + save(); + } + }); + } + }, + 10000); + } + + private void updateRecent(String dir) { + var without = FileNames.removeTrailingSlash(dir); + var with = FileNames.toDirectory(dir); + recentDirectories.removeIf(recentEntry -> + Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with)); + + var o = new RecentEntry(with, Instant.now()); + if (recentDirectories.size() < STORED) { + recentDirectories.addFirst(o); + } else { + recentDirectories.removeLast(); + recentDirectories.addFirst(o); + } + } + public static class Serializer extends StdSerializer { protected Serializer() { @@ -79,14 +156,6 @@ public class OpenFileSystemSavedState { } } - static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) { - var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> { - return new OpenFileSystemSavedState(); - }); - state.setModel(model); - return state; - } - @Value @Jacksonized @Builder @@ -95,76 +164,4 @@ public class OpenFileSystemSavedState { String directory; Instant time; } - - @Setter - private OpenFileSystemModel model; - - private String lastDirectory; - - @NonNull - private ObservableList recentDirectories; - - public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList recentDirectories) { - this.lastDirectory = lastDirectory; - this.recentDirectories = recentDirectories; - } - - private static final Timer TIMEOUT_TIMER = new Timer(true); - private static final int STORED = 10; - - public OpenFileSystemSavedState() { - lastDirectory = null; - recentDirectories = FXCollections.observableList(new ArrayList<>(STORED)); - } - - public void save() { - if (model == null) { - return; - } - - AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this); - } - - public void cd(String dir) { - if (dir == null) { - lastDirectory = null; - return; - } - - lastDirectory = dir; - // After 10 seconds - TIMEOUT_TIMER.schedule( - new TimerTask() { - @Override - public void run() { - // Synchronize with platform thread - Platform.runLater(() -> { - if (model.isClosed()) { - return; - } - - if (Objects.equals(lastDirectory, dir)) { - updateRecent(dir); - save(); - } - }); - } - }, - 10000); - } - - private void updateRecent(String dir) { - var without = FileNames.removeTrailingSlash(dir); - var with = FileNames.toDirectory(dir); - recentDirectories.removeIf(recentEntry -> - Objects.equals(recentEntry.directory, without) || Objects.equals(recentEntry.directory, with)); - - var o = new RecentEntry(with, Instant.now()); - if (recentDirectories.size() < STORED) { - recentDirectories.addFirst(o); - } else { - recentDirectories.removeLast(); - recentDirectories.addFirst(o); - } - } } diff --git a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java b/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java index 924226d7d..35e70e95f 100644 --- a/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java +++ b/app/src/main/java/io/xpipe/app/browser/StandaloneFileBrowser.java @@ -39,7 +39,8 @@ public class StandaloneFileBrowser { }); } - public static void openSingleFile(Supplier> store, Consumer file) { + public static void openSingleFile( + Supplier> store, Consumer file) { PlatformThread.runLaterIfNeeded(() -> { var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER, null); var comp = new BrowserComp(model) diff --git a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java index e2d0269cf..0d2cce15b 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java @@ -13,14 +13,6 @@ import java.util.ServiceLoader; public interface BrowserAction { - enum Category { - CUSTOM, - OPEN, - NATIVE, - COPY_PASTE, - MUTATION - } - List ALL = new ArrayList<>(); static List getFlattened(OpenFileSystemModel model, List entries) { @@ -75,6 +67,14 @@ public interface BrowserAction { return true; } + enum Category { + CUSTOM, + OPEN, + NATIVE, + COPY_PASTE, + MUTATION + } + class Loader implements ModuleLayerLoader { @Override diff --git a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java index 4e5a77792..89b7a6713 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java @@ -52,7 +52,8 @@ public interface LeafAction extends BrowserAction { b.setDisable(!isActive(model, selected)); }); - if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) { + if (getProFeatureId() != null + && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) { b.setDisable(true); b.setGraphic(new FontIcon("mdi2p-professional-hexagon")); } @@ -83,7 +84,8 @@ public interface LeafAction extends BrowserAction { mi.setMnemonicParsing(false); mi.setDisable(!isActive(model, selected)); - if (getProFeatureId() != null && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) { + if (getProFeatureId() != null + && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) { mi.setDisable(true); mi.setText(mi.getText() + " (Pro)"); } diff --git a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java index d48184884..39b7fe97e 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/MultiExecuteAction.java @@ -26,24 +26,28 @@ public abstract class MultiExecuteAction implements BranchAction { for (BrowserEntry entry : entries) { TerminalLauncher.open( model.getEntry().getEntry(), - FilenameUtils.getBaseName(entry.getRawFileEntry().getPath()), - model.getCurrentDirectory() != null ? model.getCurrentDirectory().getPath() : null, + FilenameUtils.getBaseName( + entry.getRawFileEntry().getPath()), + model.getCurrentDirectory() != null + ? model.getCurrentDirectory() + .getPath() + : null, pc.command(createCommand(pc, model, entry))); } }, false); } - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return AppPrefs.get().terminalType().getValue() != null; - } - @Override public String getName(OpenFileSystemModel model, List entries) { var t = AppPrefs.get().terminalType().getValue(); return "in " + (t != null ? t.toTranslatedString() : "?"); } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return AppPrefs.get().terminalType().getValue() != null; + } }, new LeafAction() { @@ -52,7 +56,8 @@ public abstract class MultiExecuteAction implements BranchAction { model.withShell( pc -> { for (BrowserEntry entry : entries) { - var cmd = ApplicationHelper.createDetachCommand(pc, createCommand(pc, model, entry)); + var cmd = ApplicationHelper.createDetachCommand( + pc, createCommand(pc, model, entry)); pc.command(cmd) .withWorkingDirectory(model.getCurrentDirectory() .getPath()) diff --git a/app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java b/app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java index d24325045..868165fda 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/DirectoryType.java @@ -71,6 +71,12 @@ public interface DirectoryType { }); } + String getId(); + + boolean matches(FileSystem.FileEntry entry); + + String getIcon(FileSystem.FileEntry entry, boolean open); + class Simple implements DirectoryType { @Getter @@ -101,10 +107,4 @@ public interface DirectoryType { return open ? this.open.getIcon() : this.closed.getIcon(); } } - - String getId(); - - boolean matches(FileSystem.FileEntry entry); - - String getIcon(FileSystem.FileEntry entry, boolean open); } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/FileType.java b/app/src/main/java/io/xpipe/app/browser/icon/FileType.java index 4b81099ad..f6017aac8 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/FileType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/FileType.java @@ -53,6 +53,12 @@ public interface FileType { }); } + String getId(); + + boolean matches(FileSystem.FileEntry entry); + + String getIcon(); + @Getter class Simple implements FileType { @@ -72,7 +78,9 @@ public interface FileType { return false; } - return (entry.getExtension() != null && endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT))) || endings.contains(entry.getName()); + return (entry.getExtension() != null + && endings.contains("." + entry.getExtension().toLowerCase(Locale.ROOT))) + || endings.contains(entry.getName()); } @Override @@ -80,10 +88,4 @@ public interface FileType { return icon.getIcon(); } } - - String getId(); - - boolean matches(FileSystem.FileEntry entry); - - String getIcon(); } diff --git a/app/src/main/java/io/xpipe/app/comp/base/DialogComp.java b/app/src/main/java/io/xpipe/app/comp/base/DialogComp.java index 62ce5186e..f206c8f69 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/DialogComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/DialogComp.java @@ -51,7 +51,10 @@ public abstract class DialogComp extends Comp> { buttons.setSpacing(5); buttons.setAlignment(Pos.CENTER_RIGHT); - buttons.getChildren().addAll(customButtons().stream().map(buttonComp -> buttonComp.createRegion()).toList()); + buttons.getChildren() + .addAll(customButtons().stream() + .map(buttonComp -> buttonComp.createRegion()) + .toList()); var nextButton = new ButtonComp(AppI18n.observable("finishStep"), null, this::finish) .apply(struc -> struc.get().setDefaultButton(true)) .styleClass(Styles.ACCENT) diff --git a/app/src/main/java/io/xpipe/app/comp/base/LazyTextFieldComp.java b/app/src/main/java/io/xpipe/app/comp/base/LazyTextFieldComp.java index 7ffaba463..6cd7c4178 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/LazyTextFieldComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/LazyTextFieldComp.java @@ -68,8 +68,7 @@ public class LazyTextFieldComp extends Comp { SimpleChangeListener.apply(currentValue, n -> { PlatformThread.runLaterIfNeeded(() -> { // Check if control value is the same. Then don't set it as that might cause bugs - if (Objects.equals(r.getText(), n) - || (n == null && r.getText().isEmpty())) { + if (Objects.equals(r.getText(), n) || (n == null && r.getText().isEmpty())) { return; } diff --git a/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java b/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java index abdd31360..13e5f7f26 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ListBoxViewComp.java @@ -66,7 +66,8 @@ public class ListBoxViewComp extends Comp> { return new SimpleCompStructure<>(scroll); } - private void refresh(VBox listView, List shown, List all, Map cache, boolean asynchronous) { + private void refresh( + VBox listView, List shown, List all, Map cache, boolean asynchronous) { Runnable update = () -> { // Clear cache of unused values cache.keySet().removeIf(t -> !all.contains(t)); diff --git a/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java b/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java index 552c53ad4..c605fffc4 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ListSelectorComp.java @@ -65,7 +65,8 @@ public class ListSelectorComp extends SimpleComp { if (showAllSelector) { var allSelector = new CheckBox(null); - allSelector.setSelected(values.stream().filter(t -> !disable.test(t)).count() == selected.size()); + allSelector.setSelected( + values.stream().filter(t -> !disable.test(t)).count() == selected.size()); allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> { cbs.forEach(checkBox -> { if (checkBox.isDisabled()) { diff --git a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java index 679ea1197..8ba58d756 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/LoadingOverlayComp.java @@ -18,21 +18,19 @@ public class LoadingOverlayComp extends Comp> { private static final double FPS = 30.0; private static final double cycleDurationSeconds = 4.0; - - public static LoadingOverlayComp noProgress(Comp comp, ObservableValue loading) { - return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1)); - } - private final Comp comp; private final ObservableValue showLoading; private final ObservableValue progress; - public LoadingOverlayComp(Comp comp, ObservableValue loading, ObservableValue progress) { this.comp = comp; this.showLoading = PlatformThread.sync(loading); this.progress = PlatformThread.sync(progress); } + public static LoadingOverlayComp noProgress(Comp comp, ObservableValue loading) { + return new LoadingOverlayComp(comp, loading, new SimpleDoubleProperty(-1)); + } + @Override public CompStructure createBase() { var compStruc = comp.createStructure(); @@ -42,10 +40,10 @@ public class LoadingOverlayComp extends Comp> { loading.progressProperty().bind(progress); loading.visibleProperty().bind(Bindings.not(AppPrefs.get().performanceMode())); -// var pane = new StackPane(); -// Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode(); -// pane.getChildren().add(node); -// pane.setAlignment(Pos.CENTER); + // var pane = new StackPane(); + // Parent node = new Indicator((int) (FPS * cycleDurationSeconds), 2.0).getNode(); + // pane.getChildren().add(node); + // pane.setAlignment(Pos.CENTER); var loadingOverlay = new StackPane(loading); loadingOverlay.getStyleClass().add("loading-comp"); diff --git a/app/src/main/java/io/xpipe/app/comp/base/MarkdownComp.java b/app/src/main/java/io/xpipe/app/comp/base/MarkdownComp.java index 2ae55e47b..dacbcc955 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/MarkdownComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/MarkdownComp.java @@ -46,11 +46,14 @@ public class MarkdownComp extends Comp> { @SneakyThrows private WebView createWebView() { var wv = new WebView(); - wv.getEngine().setUserDataDirectory(AppProperties.get().getDataDir().resolve("webview").toFile()); + wv.getEngine() + .setUserDataDirectory( + AppProperties.get().getDataDir().resolve("webview").toFile()); wv.setPageFill(Color.TRANSPARENT); - var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark() ? "web/github-markdown-dark.css" : "web/github-markdown-light.css"; - var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme) - .orElseThrow(); + var theme = AppPrefs.get() != null && AppPrefs.get().theme.getValue().isDark() + ? "web/github-markdown-dark.css" + : "web/github-markdown-light.css"; + var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow(); wv.getEngine().setUserStyleSheetLocation(url.toString()); SimpleChangeListener.apply(PlatformThread.sync(markdown), val -> { diff --git a/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java index 353c33591..1bc3f4c45 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/ModalOverlayComp.java @@ -20,23 +20,14 @@ import lombok.Value; public class ModalOverlayComp extends SimpleComp { + private final Comp background; + private final Property overlayContent; + public ModalOverlayComp(Comp background, Property overlayContent) { this.background = background; this.overlayContent = overlayContent; } - @Value - public static class OverlayContent { - - String titleKey; - Comp content; - String finishKey; - Runnable onFinish; - } - - private final Comp background; - private final Property overlayContent; - @Override protected Region createSimple() { var bgRegion = background.createRegion(); @@ -93,4 +84,13 @@ public class ModalOverlayComp extends SimpleComp { }); return pane; } + + @Value + public static class OverlayContent { + + String titleKey; + Comp content; + String finishKey; + Runnable onFinish; + } } diff --git a/app/src/main/java/io/xpipe/app/comp/base/OsLogoComp.java b/app/src/main/java/io/xpipe/app/comp/base/OsLogoComp.java index 68ea67975..e16caac80 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/OsLogoComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/OsLogoComp.java @@ -20,13 +20,15 @@ import java.util.Map; public class OsLogoComp extends SimpleComp { + private static final Map ICONS = new HashMap<>(); + private static final String LINUX_DEFAULT = "linux-24.png"; + private static final String LINUX_DEFAULT_SVG = "linux.svg"; private final StoreEntryWrapper wrapper; private final ObservableValue state; public OsLogoComp(StoreEntryWrapper wrapper) { this(wrapper, new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); } - public OsLogoComp(StoreEntryWrapper wrapper, ObservableValue state) { this.wrapper = wrapper; this.state = state; @@ -47,7 +49,8 @@ public class OsLogoComp extends SimpleComp { return getImage(ons.getOsName()); }, - wrapper.getPersistentState(), state)); + wrapper.getPersistentState(), + state)); var hide = BindingsHelper.map(img, s -> s != null); return new StackComp(List.of( new SystemStateComp(state).hide(hide), @@ -55,10 +58,6 @@ public class OsLogoComp extends SimpleComp { .createRegion(); } - private static final Map ICONS = new HashMap<>(); - private static final String LINUX_DEFAULT = "linux-24.png"; - private static final String LINUX_DEFAULT_SVG = "linux.svg"; - private String getImage(String name) { if (name == null) { return null; @@ -67,15 +66,21 @@ public class OsLogoComp extends SimpleComp { if (ICONS.isEmpty()) { AppResources.with(AppResources.XPIPE_MODULE, "img/os", file -> { try (var list = Files.list(file)) { - list.filter(path -> path.toString().endsWith(".svg") && !path.toString().endsWith(LINUX_DEFAULT_SVG)) - .map(path -> FileNames.getFileName(path.toString())).forEach(path -> { - var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png"; - ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base); - }); + list.filter(path -> path.toString().endsWith(".svg") + && !path.toString().endsWith(LINUX_DEFAULT_SVG)) + .map(path -> FileNames.getFileName(path.toString())) + .forEach(path -> { + var base = FileNames.getBaseName(path).replace("-dark", "") + "-24.png"; + ICONS.put(FileNames.getBaseName(base).split("-")[0], "os/" + base); + }); } }); } - return ICONS.entrySet().stream().filter(e->name.toLowerCase().contains(e.getKey())).findAny().map(e->e.getValue()).orElse("os/" + LINUX_DEFAULT); + return ICONS.entrySet().stream() + .filter(e -> name.toLowerCase().contains(e.getKey())) + .findAny() + .map(e -> e.getValue()) + .orElse("os/" + LINUX_DEFAULT); } } diff --git a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java index d9eaee7e4..831398246 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/SideMenuBarComp.java @@ -40,22 +40,31 @@ public class SideMenuBarComp extends Comp> { var vbox = new VBox(); vbox.setFillWidth(true); - var selectedBorder = Bindings.createObjectBinding(() -> { - var c = Platform.getPreferences().getAccentColor(); - return new Border(new BorderStroke(c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, - new BorderWidths(0, 3, 0, 0))); - }, Platform.getPreferences().accentColorProperty()); + var selectedBorder = Bindings.createObjectBinding( + () -> { + var c = Platform.getPreferences().getAccentColor(); + return new Border(new BorderStroke( + c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0))); + }, + Platform.getPreferences().accentColorProperty()); - var hoverBorder = Bindings.createObjectBinding(() -> { - var c = Platform.getPreferences().getAccentColor().darker(); - return new Border(new BorderStroke(c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, - new BorderWidths(0, 3, 0, 0))); - }, Platform.getPreferences().accentColorProperty()); + var hoverBorder = Bindings.createObjectBinding( + () -> { + var c = Platform.getPreferences().getAccentColor().darker(); + return new Border(new BorderStroke( + c, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 3, 0, 0))); + }, + Platform.getPreferences().accentColorProperty()); - var noneBorder = Bindings.createObjectBinding(() -> { - return new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, - new BorderWidths(0, 3, 0, 0))); - }, Platform.getPreferences().accentColorProperty()); + var noneBorder = Bindings.createObjectBinding( + () -> { + return new Border(new BorderStroke( + Color.TRANSPARENT, + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 3, 0, 0))); + }, + Platform.getPreferences().accentColorProperty()); var selected = PseudoClass.getPseudoClass("selected"); entries.forEach(e -> { @@ -68,43 +77,59 @@ public class SideMenuBarComp extends Comp> { struc.get().pseudoClassStateChanged(selected, n.equals(e)); }); }); - struc.get().borderProperty().bind(Bindings.createObjectBinding(() -> { - if (value.getValue().equals(e)) { - return selectedBorder.get(); - } + struc.get() + .borderProperty() + .bind(Bindings.createObjectBinding( + () -> { + if (value.getValue().equals(e)) { + return selectedBorder.get(); + } - if (struc.get().isHover()) { - return hoverBorder.get(); - } + if (struc.get().isHover()) { + return hoverBorder.get(); + } - return noneBorder.get(); - }, struc.get().hoverProperty(), value, hoverBorder, selectedBorder, noneBorder)); + return noneBorder.get(); + }, + struc.get().hoverProperty(), + value, + hoverBorder, + selectedBorder, + noneBorder)); }); b.accessibleText(e.name()); vbox.getChildren().add(b.createRegion()); }); Augment> simpleBorders = struc -> { - struc.get().borderProperty().bind(Bindings.createObjectBinding(() -> { - if (struc.get().isHover()) { - return hoverBorder.get(); - } + struc.get() + .borderProperty() + .bind(Bindings.createObjectBinding( + () -> { + if (struc.get().isHover()) { + return hoverBorder.get(); + } - return noneBorder.get(); - }, struc.get().hoverProperty(), value, hoverBorder, selectedBorder, noneBorder)); + return noneBorder.get(); + }, + struc.get().hoverProperty(), + value, + hoverBorder, + selectedBorder, + noneBorder)); }; { - var b = new IconButtonComp( - "mdal-bug_report", - () -> { + var b = new IconButtonComp("mdal-bug_report", () -> { var event = ErrorEvent.fromMessage("User Report"); if (AppLogs.get().isWriteToFile()) { event.attachment(AppLogs.get().getSessionLogsDirectory()); } UserReportComp.show(event.build()); }) - .apply(new FancyTooltipAugment<>("reportIssue")).apply(simpleBorders).accessibleTextKey("reportIssue"); + .apply(new FancyTooltipAugment<>("reportIssue")) + .apply(simpleBorders) + .accessibleTextKey("reportIssue"); b.apply(struc -> { AppFont.setSize(struc.get(), 2); }); @@ -113,7 +138,9 @@ public class SideMenuBarComp extends Comp> { { var b = new IconButtonComp("mdi2g-github", () -> Hyperlinks.open(Hyperlinks.GITHUB)) - .apply(new FancyTooltipAugment<>("visitGithubRepository")).apply(simpleBorders).accessibleTextKey("visitGithubRepository"); + .apply(new FancyTooltipAugment<>("visitGithubRepository")) + .apply(simpleBorders) + .accessibleTextKey("visitGithubRepository"); b.apply(struc -> { AppFont.setSize(struc.get(), 2); }); @@ -122,7 +149,9 @@ public class SideMenuBarComp extends Comp> { { var b = new IconButtonComp("mdi2d-discord", () -> Hyperlinks.open(Hyperlinks.DISCORD)) - .apply(new FancyTooltipAugment<>("discord")).apply(simpleBorders).accessibleTextKey("discord"); + .apply(new FancyTooltipAugment<>("discord")) + .apply(simpleBorders) + .accessibleTextKey("discord"); b.apply(struc -> { AppFont.setSize(struc.get(), 2); }); @@ -131,7 +160,8 @@ public class SideMenuBarComp extends Comp> { { var b = new IconButtonComp("mdi2u-update", () -> UpdateAvailableAlert.showIfNeeded()) - .apply(new FancyTooltipAugment<>("updateAvailableTooltip")).accessibleTextKey("updateAvailableTooltip"); + .apply(new FancyTooltipAugment<>("updateAvailableTooltip")) + .accessibleTextKey("updateAvailableTooltip"); b.apply(struc -> { AppFont.setSize(struc.get(), 2); }); diff --git a/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java b/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java index b1a0de5de..66fa7ffa9 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/SideSplitPaneComp.java @@ -15,6 +15,7 @@ public class SideSplitPaneComp extends Comp { private final Comp center; private Double initialWidth; private Consumer onDividerChange; + public SideSplitPaneComp(Comp left, Comp center) { this.left = left; this.center = center; diff --git a/app/src/main/java/io/xpipe/app/comp/base/SystemStateComp.java b/app/src/main/java/io/xpipe/app/comp/base/SystemStateComp.java index 87289a72d..0861c0b5a 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/SystemStateComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/SystemStateComp.java @@ -18,29 +18,12 @@ import org.kordamp.ikonli.javafx.StackedFontIcon; @Getter public class SystemStateComp extends SimpleComp { + private final ObservableValue state; public SystemStateComp(ObservableValue state) { this.state = state; } - public enum State { - FAILURE, - SUCCESS, - OTHER; - - public static ObservableValue shellState(StoreEntryWrapper w) { - return BindingsHelper.map(w.getPersistentState(),o -> { - if (o instanceof ShellStoreState shellStoreState) { - return shellStoreState.getRunning() != null ? shellStoreState.getRunning() ? SUCCESS : FAILURE : OTHER; - } - - return OTHER; - }); - } - } - - private final ObservableValue state; - @Override protected Region createSimple() { var icon = PlatformThread.sync(Bindings.createStringBinding( @@ -58,15 +41,19 @@ public class SystemStateComp extends SimpleComp { border.getStyleClass().add("outer-icon"); border.setOpacity(0.5); - var success = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }"); - var failure = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }"); - var other = Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }"); + var success = Styles.toDataURI( + ".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-success-emphasis; }"); + var failure = + Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-danger-emphasis; }"); + var other = + Styles.toDataURI(".stacked-ikonli-font-icon > .outer-icon { -fx-icon-color: -color-accent-emphasis; }"); var pane = new StackedFontIcon(); pane.getChildren().addAll(fi, border); pane.setAlignment(Pos.CENTER); - var dataClass1 = """ + var dataClass1 = + """ .stacked-ikonli-font-icon > .outer-icon { -fx-icon-size: 22px; } @@ -78,9 +65,27 @@ public class SystemStateComp extends SimpleComp { SimpleChangeListener.apply(PlatformThread.sync(state), val -> { pane.getStylesheets().removeAll(success, failure, other); - pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure: other); + pane.getStylesheets().add(val == State.SUCCESS ? success : val == State.FAILURE ? failure : other); }); return pane; } + + public enum State { + FAILURE, + SUCCESS, + OTHER; + + public static ObservableValue shellState(StoreEntryWrapper w) { + return BindingsHelper.map(w.getPersistentState(), o -> { + if (o instanceof ShellStoreState shellStoreState) { + return shellStoreState.getRunning() != null + ? shellStoreState.getRunning() ? SUCCESS : FAILURE + : OTHER; + } + + return OTHER; + }); + } + } } diff --git a/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java b/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java index 7d30e26a9..bb6f9c387 100644 --- a/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/TileButtonComp.java @@ -28,6 +28,17 @@ import java.util.function.Consumer; @Getter public class TileButtonComp extends Comp { + private final ObservableValue name; + private final ObservableValue description; + private final ObservableValue icon; + private final Consumer action; + public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer action) { + this.name = AppI18n.observable(nameKey); + this.description = AppI18n.observable(descriptionKey); + this.icon = new SimpleStringProperty(icon); + this.action = action; + } + @Override public Structure createBase() { var bt = new Button(); @@ -68,7 +79,13 @@ public class TileButtonComp extends Comp { fi.setIconSize((int) (size * 0.55)); }); bt.setGraphic(hbox); - return Structure.builder().graphic(fi).button(bt).content(hbox).name(header).description(desc).build(); + return Structure.builder() + .graphic(fi) + .button(bt) + .content(hbox) + .name(header) + .description(desc) + .build(); } @Value @@ -85,16 +102,4 @@ public class TileButtonComp extends Comp { return button; } } - - private final ObservableValue name; - private final ObservableValue description; - private final ObservableValue icon; - private final Consumer action; - - public TileButtonComp(String nameKey, String descriptionKey, String icon, Consumer action) { - this.name = AppI18n.observable(nameKey); - this.description = AppI18n.observable(descriptionKey); - this.icon = new SimpleStringProperty(icon); - this.action = action; - } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java index 78dfb8cdd..61271b613 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/DenseStoreEntryComp.java @@ -34,14 +34,22 @@ public class DenseStoreEntryComp extends StoreEntryComp { var info = wrapper.getEntry().getProvider().informationString(wrapper); var summary = wrapper.getSummary(); if (wrapper.getEntry().getProvider() != null) { - information.textProperty().bind(PlatformThread.sync(Bindings.createStringBinding(() -> { - var val = summary.getValue(); - if (val != null && grid.isHover() && wrapper.getEntry().getProvider().alwaysShowSummary()) { - return val; - } else { - return info.getValue(); - } - }, grid.hoverProperty(), info, summary))); + information + .textProperty() + .bind(PlatformThread.sync(Bindings.createStringBinding( + () -> { + var val = summary.getValue(); + if (val != null + && grid.isHover() + && wrapper.getEntry().getProvider().alwaysShowSummary()) { + return val; + } else { + return info.getValue(); + } + }, + grid.hoverProperty(), + info, + summary))); } return information; @@ -52,9 +60,12 @@ public class DenseStoreEntryComp extends StoreEntryComp { grid.setHgap(8); var name = createName().createRegion(); - name.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> { - return grid.getWidth() / 2.5; - }, grid.widthProperty())); + name.maxWidthProperty() + .bind(Bindings.createDoubleBinding( + () -> { + return grid.getWidth() / 2.5; + }, + grid.widthProperty())); if (showIcon) { var storeIcon = createIcon(30, 24); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java index 6e402c56c..e6b4e72ca 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StandardStoreEntryComp.java @@ -12,7 +12,6 @@ public class StandardStoreEntryComp extends StoreEntryComp { super(entry, content); } - protected Region createContent() { var name = createName().createRegion(); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCategoryWrapper.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCategoryWrapper.java index 3755e8017..61464a1ff 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCategoryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCategoryWrapper.java @@ -59,8 +59,9 @@ public class StoreCategoryWrapper { public StoreCategoryWrapper getParent() { return StoreViewState.get().getCategories().stream() .filter(storeCategoryWrapper -> - storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory())) - .findAny().orElse(null); + storeCategoryWrapper.getCategory().getUuid().equals(category.getParentCategory())) + .findAny() + .orElse(null); } public boolean contains(DataStoreEntry entry) { @@ -97,8 +98,8 @@ public class StoreCategoryWrapper { DataStoreCategory p = category; if (newValue) { while ((p = DataStorage.get() - .getStoreCategoryIfPresent(p.getParentCategory()) - .orElse(null)) + .getStoreCategoryIfPresent(p.getParentCategory()) + .orElse(null)) != null) { p.setShare(true); } @@ -124,10 +125,9 @@ public class StoreCategoryWrapper { .getUuid() .equals(storeCategoryWrapper.getCategory().getParentCategory())) .toList()); - Optional.ofNullable(getParent()) - .ifPresent(storeCategoryWrapper -> { - storeCategoryWrapper.update(); - }); + Optional.ofNullable(getParent()).ifPresent(storeCategoryWrapper -> { + storeCategoryWrapper.update(); + }); } public String getName() { diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java index bf9fcfbaa..1c891d008 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationComp.java @@ -61,7 +61,8 @@ public class StoreCreationComp extends DialogComp { boolean staticDisplay; public StoreCreationComp( - Stage window, Consumer consumer, + Stage window, + Consumer consumer, Property provider, Property store, Predicate filter, @@ -101,36 +102,42 @@ public class StoreCreationComp extends DialogComp { newValue.validate(); }); }); - this.entry = Bindings.createObjectBinding(() -> { - if (name.getValue() == null || store.getValue() == null) { - return null; - } + this.entry = Bindings.createObjectBinding( + () -> { + if (name.getValue() == null || store.getValue() == null) { + return null; + } - var testE = DataStoreEntry.createNew( - UUID.randomUUID(), - DataStorage.get().getSelectedCategory().getUuid(), - name.getValue(), - store.getValue()); - var p = provider.getValue().getDisplayParent(testE); + var testE = DataStoreEntry.createNew( + UUID.randomUUID(), + DataStorage.get().getSelectedCategory().getUuid(), + name.getValue(), + store.getValue()); + var p = provider.getValue().getDisplayParent(testE); - var targetCategory = p != null - ? p.getCategoryUuid() - : DataStorage.get() - .getSelectedCategory() - .getUuid(); - var rootCategory = DataStorage.get().getRootCategory(DataStorage.get().getStoreCategoryIfPresent(targetCategory).orElseThrow()); - // Don't put connections in the scripts category ever - if ((provider.getValue().getCreationCategory() == null || !provider.getValue().getCreationCategory().equals(DataStoreProvider.CreationCategory.SCRIPT)) && - rootCategory.equals(DataStorage.get().getAllScriptsCategory())) { - targetCategory = DataStorage.get().getDefaultConnectionsCategory().getUuid(); - } + var targetCategory = p != null + ? p.getCategoryUuid() + : DataStorage.get().getSelectedCategory().getUuid(); + var rootCategory = DataStorage.get() + .getRootCategory(DataStorage.get() + .getStoreCategoryIfPresent(targetCategory) + .orElseThrow()); + // Don't put connections in the scripts category ever + if ((provider.getValue().getCreationCategory() == null + || !provider.getValue() + .getCreationCategory() + .equals(DataStoreProvider.CreationCategory.SCRIPT)) + && rootCategory.equals(DataStorage.get().getAllScriptsCategory())) { + targetCategory = DataStorage.get() + .getDefaultConnectionsCategory() + .getUuid(); + } - return DataStoreEntry.createNew( - UUID.randomUUID(), - targetCategory, - name.getValue(), - store.getValue()); - }, name, store); + return DataStoreEntry.createNew( + UUID.randomUUID(), targetCategory, name.getValue(), store.getValue()); + }, + name, + store); } public static void showEdit(DataStoreEntry e) { @@ -186,60 +193,18 @@ public class StoreCreationComp extends DialogComp { DataStoreEntry existingEntry) { var prop = new SimpleObjectProperty<>(provider); var store = new SimpleObjectProperty<>(s); - DialogComp.showWindow("addConnection", stage -> new StoreCreationComp( - stage, con, prop, store, filter, initialName, existingEntry, staticDisplay)); - } - - @Override - protected List> customButtons() { - return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> { - if (showInvalidConfirmAlert()) { - commit(); - } else { - finish(); - } - }).visible(skippable)); - } - - @Override - protected ObservableValue busy() { - return busy; - } - - @Override - public Comp bottom() { - var disable = Bindings.createBooleanBinding( - () -> { - return provider.getValue() == null - || store.getValue() == null - || !store.getValue().isComplete() - // When switching providers, both observables change one after another. - // So temporarily there might be a store class mismatch - || provider.getValue().getStoreClasses().stream().noneMatch(aClass -> aClass.isAssignableFrom(store.getValue().getClass())) - || provider.getValue().createInsightsMarkdown(store.getValue()) == null; - }, - provider, - store); - return new PopupMenuButtonComp( - new SimpleStringProperty("Insights >"), - Comp.of(() -> { - return provider.getValue() != null - ? provider.getValue() - .createInsightsComp(store) - .createRegion() - : null; - }), - true) - .hide(disable) - .styleClass("button-comp"); + DialogComp.showWindow( + "addConnection", + stage -> new StoreCreationComp( + stage, con, prop, store, filter, initialName, existingEntry, staticDisplay)); } private static boolean showInvalidConfirmAlert() { return AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("confirmInvalidStoreTitle")); alert.setHeaderText(AppI18n.get("confirmInvalidStoreHeader")); - alert.getDialogPane().setContent(AppWindowHelper.alertContentText( - AppI18n.get("confirmInvalidStoreContent"))); + alert.getDialogPane() + .setContent(AppWindowHelper.alertContentText(AppI18n.get("confirmInvalidStoreContent"))); alert.setAlertType(Alert.AlertType.CONFIRMATION); alert.getButtonTypes().clear(); alert.getButtonTypes().add(new ButtonType("Retry", ButtonBar.ButtonData.CANCEL_CLOSE)); @@ -249,29 +214,21 @@ public class StoreCreationComp extends DialogComp { .orElse(false); } - private Region createStoreProperties(Comp comp, Validator propVal) { - return new OptionsBuilder() - .addComp(comp, store) - .name("connectionName") - .description("connectionNameDescription") - .addString(name, false) - .nonNull(propVal) - .build(); + @Override + protected List> customButtons() { + return List.of(new ButtonComp(AppI18n.observable("skip"), null, () -> { + if (showInvalidConfirmAlert()) { + commit(); + } else { + finish(); + } + }) + .visible(skippable)); } - private void commit() { - if (finished.get()) { - return; - } - finished.setValue(true); - - if (entry.getValue() != null) { - consumer.accept(entry.getValue()); - } - - PlatformThread.runLaterIfNeeded(() -> { - window.close(); - }); + @Override + protected ObservableValue busy() { + return busy; } @Override @@ -336,6 +293,11 @@ public class StoreCreationComp extends DialogComp { }); } + @Override + public Comp content() { + return Comp.of(this::createLayout); + } + @Override protected Comp scrollPane(Comp content) { var back = super.scrollPane(content); @@ -343,8 +305,58 @@ public class StoreCreationComp extends DialogComp { } @Override - public Comp content() { - return Comp.of(this::createLayout); + public Comp bottom() { + var disable = Bindings.createBooleanBinding( + () -> { + return provider.getValue() == null + || store.getValue() == null + || !store.getValue().isComplete() + // When switching providers, both observables change one after another. + // So temporarily there might be a store class mismatch + || provider.getValue().getStoreClasses().stream() + .noneMatch(aClass -> aClass.isAssignableFrom( + store.getValue().getClass())) + || provider.getValue().createInsightsMarkdown(store.getValue()) == null; + }, + provider, + store); + return new PopupMenuButtonComp( + new SimpleStringProperty("Insights >"), + Comp.of(() -> { + return provider.getValue() != null + ? provider.getValue() + .createInsightsComp(store) + .createRegion() + : null; + }), + true) + .hide(disable) + .styleClass("button-comp"); + } + + private Region createStoreProperties(Comp comp, Validator propVal) { + return new OptionsBuilder() + .addComp(comp, store) + .name("connectionName") + .description("connectionNameDescription") + .addString(name, false) + .nonNull(propVal) + .build(); + } + + private void commit() { + if (finished.get()) { + return; + } + finished.setValue(true); + + if (entry.getValue() != null) { + consumer.accept(entry.getValue()); + } + + PlatformThread.runLaterIfNeeded(() -> { + window.close(); + }); } private Region createLayout() { diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java index 21d94a1ed..b206ff885 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreCreationMenu.java @@ -24,33 +24,42 @@ public class StoreCreationMenu { menu.getItems().add(automatically); menu.getItems().add(new SeparatorMenuItem()); - menu.getItems().add(category("addHost", "mdi2h-home-plus", - DataStoreProvider.CreationCategory.HOST, "ssh")); + menu.getItems().add(category("addHost", "mdi2h-home-plus", DataStoreProvider.CreationCategory.HOST, "ssh")); - menu.getItems().add(category("addShell", "mdi2t-text-box-multiple", - DataStoreProvider.CreationCategory.SHELL, null)); + menu.getItems() + .add(category("addShell", "mdi2t-text-box-multiple", DataStoreProvider.CreationCategory.SHELL, null)); - menu.getItems().add(category("addScript", "mdi2s-script-text-outline", - DataStoreProvider.CreationCategory.SCRIPT, "script")); + menu.getItems() + .add(category( + "addScript", "mdi2s-script-text-outline", DataStoreProvider.CreationCategory.SCRIPT, "script")); - menu.getItems().add(category("addCommand", "mdi2c-code-greater-than", - DataStoreProvider.CreationCategory.COMMAND, "cmd")); + menu.getItems() + .add(category( + "addCommand", "mdi2c-code-greater-than", DataStoreProvider.CreationCategory.COMMAND, "cmd")); - menu.getItems().add(category("addTunnel", "mdi2v-vector-polyline-plus", - DataStoreProvider.CreationCategory.TUNNEL, null)); + menu.getItems() + .add(category( + "addTunnel", "mdi2v-vector-polyline-plus", DataStoreProvider.CreationCategory.TUNNEL, null)); - menu.getItems().add(category("addDatabase", "mdi2d-database-plus", - DataStoreProvider.CreationCategory.DATABASE, null)); + menu.getItems() + .add(category("addDatabase", "mdi2d-database-plus", DataStoreProvider.CreationCategory.DATABASE, null)); } - private static MenuItem category(String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) { - var sub = DataStoreProviders.getAll().stream().filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())).toList(); + private static MenuItem category( + String name, String graphic, DataStoreProvider.CreationCategory category, String defaultProvider) { + var sub = DataStoreProviders.getAll().stream() + .filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())) + .toList(); if (sub.size() < 2) { var item = new MenuItem(); item.setGraphic(new FontIcon(graphic)); item.textProperty().bind(AppI18n.observable(name)); item.setOnAction(event -> { - StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null, category); + StoreCreationComp.showCreation( + defaultProvider != null + ? DataStoreProviders.byName(defaultProvider).orElseThrow() + : null, + category); event.consume(); }); return item; @@ -64,16 +73,19 @@ public class StoreCreationMenu { return; } - StoreCreationComp.showCreation(defaultProvider != null ? DataStoreProviders.byName(defaultProvider).orElseThrow() : null, + StoreCreationComp.showCreation( + defaultProvider != null + ? DataStoreProviders.byName(defaultProvider).orElseThrow() + : null, category); event.consume(); }); sub.forEach(dataStoreProvider -> { var item = new MenuItem(dataStoreProvider.getDisplayName()); - item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null)).createRegion()); + item.setGraphic(PrettyImageHelper.ofFixedSmallSquare(dataStoreProvider.getDisplayIconFileName(null)) + .createRegion()); item.setOnAction(event -> { - StoreCreationComp.showCreation(dataStoreProvider, - category); + StoreCreationComp.showCreation(dataStoreProvider, category); event.consume(); }); menu.getItems().add(item); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryComp.java index df8d60921..de715bb9e 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryComp.java @@ -40,9 +40,22 @@ import java.util.Arrays; public abstract class StoreEntryComp extends SimpleComp { - public static StoreEntryComp create( - StoreEntryWrapper entry, Comp content, boolean preferLarge) { - var forceCondensed = AppPrefs.get() != null && AppPrefs.get().condenseConnectionDisplay().get(); + public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed"); + public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete"); + public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH = + App.getApp().getStage().widthProperty().divide(2.2).add(-100); + public static final ObservableDoubleValue INFO_WITH_CONTENT_WIDTH = + App.getApp().getStage().widthProperty().divide(2.2).add(-200); + protected final StoreEntryWrapper wrapper; + protected final Comp content; + public StoreEntryComp(StoreEntryWrapper wrapper, Comp content) { + this.wrapper = wrapper; + this.content = content; + } + + public static StoreEntryComp create(StoreEntryWrapper entry, Comp content, boolean preferLarge) { + var forceCondensed = AppPrefs.get() != null + && AppPrefs.get().condenseConnectionDisplay().get(); if (!preferLarge || forceCondensed) { return new DenseStoreEntryComp(entry, true, content); } else { @@ -55,27 +68,14 @@ public abstract class StoreEntryComp extends SimpleComp { if (prov != null) { return prov.customEntryComp(e, topLevel); } else { - var forceCondensed = AppPrefs.get() != null && AppPrefs.get().condenseConnectionDisplay().get(); - return forceCondensed ? - new DenseStoreEntryComp(e.getWrapper(), true, null) : - new StandardStoreEntryComp(e.getWrapper(), null); + var forceCondensed = AppPrefs.get() != null + && AppPrefs.get().condenseConnectionDisplay().get(); + return forceCondensed + ? new DenseStoreEntryComp(e.getWrapper(), true, null) + : new StandardStoreEntryComp(e.getWrapper(), null); } } - public static final PseudoClass FAILED = PseudoClass.getPseudoClass("failed"); - public static final PseudoClass INCOMPLETE = PseudoClass.getPseudoClass("incomplete"); - public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH = - App.getApp().getStage().widthProperty().divide(2.2).add(-100); - public static final ObservableDoubleValue INFO_WITH_CONTENT_WIDTH = - App.getApp().getStage().widthProperty().divide(2.2).add(-200); - protected final StoreEntryWrapper wrapper; - protected final Comp content; - - public StoreEntryComp(StoreEntryWrapper wrapper, Comp content) { - this.wrapper = wrapper; - this.content = content; - } - @Override protected final Region createSimple() { var r = createContent(); @@ -87,8 +87,7 @@ public abstract class StoreEntryComp extends SimpleComp { button.setPadding(Insets.EMPTY); button.setMaxWidth(5000); button.setFocusTraversable(true); - button.accessibleTextProperty() - .bind(wrapper.nameProperty()); + button.accessibleTextProperty().bind(wrapper.nameProperty()); button.setOnAction(event -> { event.consume(); ThreadHelper.runFailableAsync(() -> { @@ -109,8 +108,13 @@ public abstract class StoreEntryComp extends SimpleComp { protected Label createInformation() { var information = new Label(); information.setGraphicTextGap(7); - information.textProperty().bind(wrapper.getEntry().getProvider() != null ? - PlatformThread.sync(wrapper.getEntry().getProvider().informationString(wrapper)) : new SimpleStringProperty()); + information + .textProperty() + .bind( + wrapper.getEntry().getProvider() != null + ? PlatformThread.sync( + wrapper.getEntry().getProvider().informationString(wrapper)) + : new SimpleStringProperty()); information.getStyleClass().add("information"); AppFont.header(information); @@ -195,15 +199,16 @@ public abstract class StoreEntryComp extends SimpleComp { continue; } - var button = new IconButtonComp( - actionProvider.getIcon(wrapper.getEntry().ref()), () -> { + var button = + new IconButtonComp(actionProvider.getIcon(wrapper.getEntry().ref()), () -> { ThreadHelper.runFailableAsync(() -> { var action = actionProvider.createAction( wrapper.getEntry().ref()); action.execute(); }); }); - button.accessibleText(actionProvider.getName(wrapper.getEntry().ref()).getValue()); + button.accessibleText( + actionProvider.getName(wrapper.getEntry().ref()).getValue()); button.apply(new FancyTooltipAugment<>( actionProvider.getName(wrapper.getEntry().ref()))); if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) { @@ -268,11 +273,13 @@ public abstract class StoreEntryComp extends SimpleComp { ? new Menu(null, new FontIcon(icon)) : new MenuItem(null, new FontIcon(icon)); - var proRequired = p.getKey().getProFeatureId() != null && - !LicenseProvider.get().getFeature(p.getKey().getProFeatureId()).isSupported(); + var proRequired = p.getKey().getProFeatureId() != null + && !LicenseProvider.get() + .getFeature(p.getKey().getProFeatureId()) + .isSupported(); if (proRequired) { item.setDisable(true); - item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)",name)); + item.textProperty().bind(Bindings.createStringBinding(() -> name.getValue() + " (Pro)", name)); } else { item.textProperty().bind(name); } @@ -289,8 +296,7 @@ public abstract class StoreEntryComp extends SimpleComp { contextMenu.hide(); ThreadHelper.runFailableAsync(() -> { - var action = actionProvider.createAction( - wrapper.getEntry().ref()); + var action = actionProvider.createAction(wrapper.getEntry().ref()); action.execute(); }); }); @@ -306,20 +312,27 @@ public abstract class StoreEntryComp extends SimpleComp { run.textProperty().bind(AppI18n.observable("base.execute")); run.setOnAction(event -> { ThreadHelper.runFailableAsync(() -> { - p.getKey().getDataStoreCallSite().createAction(wrapper.getEntry().ref()).execute(); + p.getKey() + .getDataStoreCallSite() + .createAction(wrapper.getEntry().ref()) + .execute(); }); }); menu.getItems().add(run); - var sc = new MenuItem(null, new FontIcon("mdi2c-code-greater-than")); var url = "xpipe://action/" + p.getKey().getId() + "/" + wrapper.getEntry().getUuid(); sc.textProperty().bind(AppI18n.observable("base.createShortcut")); sc.setOnAction(event -> { ThreadHelper.runFailableAsync(() -> { - DesktopShortcuts.create(url, - wrapper.nameProperty().getValue() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().ref()).getValue() + ")"); + DesktopShortcuts.create( + url, + wrapper.nameProperty().getValue() + " (" + + p.getKey() + .getDataStoreCallSite() + .getName(wrapper.getEntry().ref()) + .getValue() + ")"); }); }); menu.getItems().add(sc); @@ -349,20 +362,23 @@ public abstract class StoreEntryComp extends SimpleComp { contextMenu.getItems().add(browse); } - if (wrapper.getEntry().getProvider() != null && wrapper.getEntry().getProvider().canMoveCategories()) { + if (wrapper.getEntry().getProvider() != null + && wrapper.getEntry().getProvider().canMoveCategories()) { var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline")); - StoreViewState.get().getSortedCategories(wrapper.getCategory().getValue().getRoot()).forEach(storeCategoryWrapper -> { - MenuItem m = new MenuItem(storeCategoryWrapper.getName()); - m.setOnAction(event -> { - wrapper.moveTo(storeCategoryWrapper.getCategory()); - event.consume(); - }); - if (storeCategoryWrapper.getParent() == null) { - m.setDisable(true); - } + StoreViewState.get() + .getSortedCategories(wrapper.getCategory().getValue().getRoot()) + .forEach(storeCategoryWrapper -> { + MenuItem m = new MenuItem(storeCategoryWrapper.getName()); + m.setOnAction(event -> { + wrapper.moveTo(storeCategoryWrapper.getCategory()); + event.consume(); + }); + if (storeCategoryWrapper.getParent() == null) { + m.setDisable(true); + } - move.getItems().add(m); - }); + move.getItems().add(m); + }); contextMenu.getItems().add(move); } @@ -386,9 +402,16 @@ public abstract class StoreEntryComp extends SimpleComp { } var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline")); - del.disableProperty().bind(Bindings.createBooleanBinding(() -> { - return !wrapper.getDeletable().get() && !AppPrefs.get().developerDisableGuiRestrictions().get(); - }, wrapper.getDeletable(), AppPrefs.get().developerDisableGuiRestrictions())); + del.disableProperty() + .bind(Bindings.createBooleanBinding( + () -> { + return !wrapper.getDeletable().get() + && !AppPrefs.get() + .developerDisableGuiRestrictions() + .get(); + }, + wrapper.getDeletable(), + AppPrefs.get().developerDisableGuiRestrictions())); del.setOnAction(event -> wrapper.delete()); contextMenu.getItems().add(del); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListComp.java index 72d5a00f4..47f07e454 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListComp.java @@ -35,10 +35,18 @@ public class StoreEntryListComp extends SimpleComp { var showIntro = Bindings.createBooleanBinding( () -> { var all = StoreViewState.get().getAllConnectionsCategory(); - var connections = StoreViewState.get().getAllEntries().stream().filter(wrapper -> all.contains(wrapper.getEntry())).toList(); - return initialCount == connections.size() && StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()); + var connections = StoreViewState.get().getAllEntries().stream() + .filter(wrapper -> all.contains(wrapper.getEntry())) + .toList(); + return initialCount == connections.size() + && StoreViewState.get() + .getActiveCategory() + .getValue() + .getRoot() + .equals(StoreViewState.get().getAllConnectionsCategory()); }, - StoreViewState.get().getAllEntries(), StoreViewState.get().getActiveCategory()); + StoreViewState.get().getAllEntries(), + StoreViewState.get().getActiveCategory()); var map = new LinkedHashMap, ObservableValue>(); map.put( createList(), diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java index cf9e8f47b..5eb91dee0 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryListStatusComp.java @@ -51,16 +51,29 @@ public class StoreEntryListStatusComp extends SimpleComp { private Region createGroupListHeader() { var label = new Label(); - label.textProperty().bind(Bindings.createStringBinding(() -> { - return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(StoreViewState.get().getAllConnectionsCategory()) ? "Connections" : "Scripts"; - }, StoreViewState.get().getActiveCategory())); + label.textProperty() + .bind(Bindings.createStringBinding( + () -> { + return StoreViewState.get() + .getActiveCategory() + .getValue() + .getRoot() + .equals(StoreViewState.get().getAllConnectionsCategory()) + ? "Connections" + : "Scripts"; + }, + StoreViewState.get().getActiveCategory())); label.getStyleClass().add("name"); var all = BindingsHelper.filteredContentBinding( StoreViewState.get().getAllEntries(), storeEntryWrapper -> { var storeRoot = storeEntryWrapper.getCategory().getValue().getRoot(); - return StoreViewState.get().getActiveCategory().getValue().getRoot().equals(storeRoot); + return StoreViewState.get() + .getActiveCategory() + .getValue() + .getRoot() + .equals(storeRoot); }, StoreViewState.get().getActiveCategory()); var shownList = BindingsHelper.filteredContentBinding( @@ -73,7 +86,13 @@ public class StoreEntryListStatusComp extends SimpleComp { var count = new CountComp<>(shownList, all); var c = count.createRegion(); - var topBar = new HBox(label, c, Comp.hspacer().createRegion(), createDateSortButton().createRegion(), Comp.hspacer(2).createRegion(), createAlphabeticalSortButton().createRegion()); + var topBar = new HBox( + label, + c, + Comp.hspacer().createRegion(), + createDateSortButton().createRegion(), + Comp.hspacer(2).createRegion(), + createAlphabeticalSortButton().createRegion()); AppFont.setSize(label, 3); AppFont.setSize(c, 3); topBar.setAlignment(Pos.CENTER); @@ -94,7 +113,7 @@ public class StoreEntryListStatusComp extends SimpleComp { }); filter.apply(struc -> struc.get().sceneProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { - struc.getText().requestFocus(); + struc.getText().requestFocus(); } })); @@ -111,7 +130,6 @@ public class StoreEntryListStatusComp extends SimpleComp { f.setPadding(new Insets(-3, 0, -3, 0)); } - AppFont.medium(hbox); return hbox; } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java index c8f693326..874e8d1ed 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreEntryWrapper.java @@ -121,7 +121,10 @@ public class StoreEntryWrapper { deletable.setValue(entry.getConfiguration().isDeletable() || AppPrefs.get().developerDisableGuiRestrictions().getValue()); - category.setValue(StoreViewState.get().getCategoryWrapper(DataStorage.get().getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow())); + category.setValue(StoreViewState.get() + .getCategoryWrapper(DataStorage.get() + .getStoreCategoryIfPresent(entry.getCategoryUuid()) + .orElseThrow())); if (!entry.getValidity().isUsable()) { summary.setValue(null); @@ -155,8 +158,7 @@ public class StoreEntryWrapper { && e.getDefaultDataStoreCallSite() .getApplicableClass() .isAssignableFrom(entry.getStore().getClass()) - && e.getDefaultDataStoreCallSite() - .isApplicable(entry.ref())) + && e.getDefaultDataStoreCallSite().isApplicable(entry.ref())) .findFirst() .map(ActionProvider::getDefaultDataStoreCallSite) .orElse(null); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java index 4cb258c1d..e508b37ff 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreIntroComp.java @@ -35,7 +35,8 @@ public class StoreIntroComp extends SimpleComp { var scanPane = new StackPane(scanButton); scanPane.setAlignment(Pos.CENTER); - var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150).createRegion(); + var img = PrettyImageHelper.ofSvg(new SimpleStringProperty("Wave.svg"), 80, 150) + .createRegion(); var text = new VBox(title, introDesc); text.setSpacing(5); text.setAlignment(Pos.CENTER_LEFT); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java index 39411d2f3..e31bd57d2 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreLayoutComp.java @@ -19,10 +19,12 @@ public class StoreLayoutComp extends SimpleComp { @Override protected Region createSimple() { - var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()).withInitialWidth( - AppLayoutModel.get().getSavedState().getSidebarWidth()).withOnDividerChange(aDouble -> { - AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble); - }).createStructure(); + var struc = new SideSplitPaneComp(new StoreSidebarComp(), new StoreEntryListComp()) + .withInitialWidth(AppLayoutModel.get().getSavedState().getSidebarWidth()) + .withOnDividerChange(aDouble -> { + AppLayoutModel.get().getSavedState().setSidebarWidth(aDouble); + }) + .createStructure(); struc.getLeft().setMinWidth(260); struc.getLeft().setMaxWidth(500); struc.get().getStyleClass().add("store-layout"); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreProviderChoiceComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreProviderChoiceComp.java index 7831c8526..e9af0f355 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreProviderChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreProviderChoiceComp.java @@ -28,7 +28,9 @@ public class StoreProviderChoiceComp extends Comp getProviders() { - return DataStoreProviders.getAll().stream().filter(val -> filter == null || filter.test(val)).toList(); + return DataStoreProviders.getAll().stream() + .filter(val -> filter == null || filter.test(val)) + .toList(); } private Region createGraphic(DataStoreProvider provider) { diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSection.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSection.java index 9952def1f..0872ef158 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSection.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSection.java @@ -19,21 +19,11 @@ import java.util.function.Predicate; @Value public class StoreSection { - public static Comp customSection(StoreSection e, boolean topLevel) { - var prov = e.getWrapper().getEntry().getProvider(); - if (prov != null) { - return prov.customSectionComp(e, topLevel); - } else { - return new StoreSectionComp(e, topLevel); - } - } - StoreEntryWrapper wrapper; ObservableList allChildren; ObservableList shownChildren; int depth; ObservableBooleanValue showDetails; - public StoreSection( StoreEntryWrapper wrapper, ObservableList allChildren, @@ -55,6 +45,15 @@ public class StoreSection { } } + public static Comp customSection(StoreSection e, boolean topLevel) { + var prov = e.getWrapper().getEntry().getProvider(); + if (prov != null) { + return prov.customSectionComp(e, topLevel); + } else { + return new StoreSectionComp(e, topLevel); + } + } + private static ObservableList sorted( ObservableList list, ObservableValue category) { if (category == null) { @@ -63,7 +62,9 @@ public class StoreSection { var c = Comparator.comparingInt( value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1); - var mappedSortMode = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null); + var mappedSortMode = BindingsHelper.mappedBinding( + category, + storeCategoryWrapper -> storeCategoryWrapper != null ? storeCategoryWrapper.getSortMode() : null); return BindingsHelper.orderedContentBinding( list, (o1, o2) -> { @@ -97,7 +98,9 @@ public class StoreSection { section -> { var showFilter = filterString == null || section.shouldShow(filterString.get()); var matchesSelector = section.anyMatches(entryFilter); - var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry()); + var sameCategory = category == null + || category.getValue() == null + || category.getValue().contains(section.getWrapper().getEntry()); return showFilter && matchesSelector && sameCategory; }, category, @@ -118,10 +121,10 @@ public class StoreSection { var allChildren = BindingsHelper.filteredContentBinding(all, other -> { // Legacy implementation that does not use children caches. Use for testing -// if (true) return DataStorage.get() -// .getDisplayParent(other.getEntry()) -// .map(found -> found.equals(e.getEntry())) -// .orElse(false); + // if (true) return DataStorage.get() + // .getDisplayParent(other.getEntry()) + // .map(found -> found.equals(e.getEntry())) + // .orElse(false); // This check is fast as the children are cached in the storage return DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry()); @@ -134,9 +137,13 @@ public class StoreSection { section -> { var showFilter = filterString == null || section.shouldShow(filterString.get()); var matchesSelector = section.anyMatches(entryFilter); - var sameCategory = category == null || category.getValue() == null || category.getValue().contains(section.getWrapper().getEntry()); - // If this entry is already shown as root due to a different category than parent, don't show it again here - var notRoot = !DataStorage.get().isRootEntry(section.getWrapper().getEntry()); + var sameCategory = category == null + || category.getValue() == null + || category.getValue().contains(section.getWrapper().getEntry()); + // If this entry is already shown as root due to a different category than parent, don't show it + // again here + var notRoot = + !DataStorage.get().isRootEntry(section.getWrapper().getEntry()); return showFilter && matchesSelector && sameCategory && notRoot; }, category, diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java index 6ed041466..531c6661b 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionComp.java @@ -22,12 +22,11 @@ import java.util.List; public class StoreSectionComp extends Comp> { + public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded"); private static final PseudoClass ROOT = PseudoClass.getPseudoClass("root"); private static final PseudoClass SUB = PseudoClass.getPseudoClass("sub"); private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth"); private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth"); - public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded"); - private final StoreSection section; private final boolean topLevel; @@ -54,9 +53,11 @@ public class StoreSectionComp extends Comp> { .apply(struc -> struc.get().setMinWidth(30)) .apply(struc -> struc.get().setPrefWidth(30)) .focusTraversable() - .accessibleText(Bindings.createStringBinding(() -> { - return "Expand " + section.getWrapper().getName().getValue(); - }, section.getWrapper().getName())) + .accessibleText(Bindings.createStringBinding( + () -> { + return "Expand " + section.getWrapper().getName().getValue(); + }, + section.getWrapper().getName())) .disable(BindingsHelper.persist( Bindings.size(section.getShownChildren()).isEqualTo(0))) .grow(false, true) diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionMiniComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionMiniComp.java index 08cbd0561..bb44082ad 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSectionMiniComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSectionMiniComp.java @@ -28,19 +28,17 @@ import java.util.function.BiConsumer; @Builder public class StoreSectionMiniComp extends Comp> { + public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded"); + private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth"); + private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth"); + private final StoreSection section; + @Builder.Default + private final BiConsumer>> augment = (section1, buttonComp) -> {}; + public static Comp createList(StoreSection top, BiConsumer>> augment) { return new StoreSectionMiniComp(top, augment); } - private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd-depth"); - private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even-depth"); - public static final PseudoClass EXPANDED = PseudoClass.getPseudoClass("expanded"); - - private final StoreSection section; - - @Builder.Default - private final BiConsumer>> augment = (section1, buttonComp) -> {}; - @Override public CompStructure createBase() { var list = new ArrayList>(); @@ -48,14 +46,14 @@ public class StoreSectionMiniComp extends Comp> { if (section.getWrapper() != null) { var root = new ButtonComp(section.getWrapper().nameProperty(), () -> {}) .apply(struc -> { - var provider = section.getWrapper() - .getEntry() - .getProvider(); + var provider = section.getWrapper().getEntry().getProvider(); struc.get() - .setGraphic(PrettyImageHelper.ofFixedSmallSquare(provider != null ? provider - .getDisplayIconFileName(section.getWrapper() - .getEntry() - .getStore()) : null) + .setGraphic(PrettyImageHelper.ofFixedSmallSquare( + provider != null + ? provider.getDisplayIconFileName(section.getWrapper() + .getEntry() + .getStore()) + : null) .createRegion()); }) .apply(struc -> { @@ -79,38 +77,47 @@ public class StoreSectionMiniComp extends Comp> { .apply(struc -> struc.get().setMinWidth(20)) .apply(struc -> struc.get().setPrefWidth(20)) .focusTraversable() - .accessibleText(Bindings.createStringBinding(() -> { - return "Expand " + section.getWrapper().getName().getValue(); - }, section.getWrapper().getName())) + .accessibleText(Bindings.createStringBinding( + () -> { + return "Expand " + + section.getWrapper().getName().getValue(); + }, + section.getWrapper().getName())) .disable(BindingsHelper.persist( Bindings.size(section.getAllChildren()).isEqualTo(0))) .grow(false, true) .styleClass("expand-button"); List> topEntryList = List.of(button, root); - list.add(new HorizontalComp(topEntryList) - .apply(struc -> struc.get().setFillHeight(true))); + list.add(new HorizontalComp(topEntryList).apply(struc -> struc.get().setFillHeight(true))); } else { expanded = new SimpleBooleanProperty(true); } // Optimization for large sections. If there are more than 20 children, only add the nodes to the scene if the // section is actually expanded - var listSections = section.getWrapper() != null ? BindingsHelper.filteredContentBinding( - section.getShownChildren(), - storeSection -> section.getAllChildren().size() <= 20 - || expanded.get(), - expanded, - section.getAllChildren()) : section.getShownChildren(); + var listSections = section.getWrapper() != null + ? BindingsHelper.filteredContentBinding( + section.getShownChildren(), + storeSection -> section.getAllChildren().size() <= 20 || expanded.get(), + expanded, + section.getAllChildren()) + : section.getShownChildren(); var content = new ListBoxViewComp<>(listSections, section.getAllChildren(), (StoreSection e) -> { - return StoreSectionMiniComp.builder().section(e).augment(this.augment).build(); - }).withLimit(100).minHeight(0).hgrow(); + return StoreSectionMiniComp.builder() + .section(e) + .augment(this.augment) + .build(); + }) + .withLimit(100) + .minHeight(0) + .hgrow(); list.add(new HorizontalComp(List.of(content)) - .styleClass("content") - .apply(struc -> struc.get().setFillHeight(true)) - .hide(BindingsHelper.persist(Bindings.or( - Bindings.not(expanded), - Bindings.size(section.getAllChildren()).isEqualTo(0))))); + .styleClass("content") + .apply(struc -> struc.get().setFillHeight(true)) + .hide(BindingsHelper.persist(Bindings.or( + Bindings.not(expanded), + Bindings.size(section.getAllChildren()).isEqualTo(0))))); return new VerticalComp(list) .styleClass("store-section-mini-comp") @@ -130,8 +137,9 @@ public class StoreSectionMiniComp extends Comp> { return; } - struc.get().getStyleClass().removeIf( - s -> Arrays.stream(DataStoreColor.values()).anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s))); + struc.get().getStyleClass().removeIf(s -> Arrays.stream(DataStoreColor.values()) + .anyMatch(dataStoreColor -> + dataStoreColor.getId().equals(s))); struc.get().getStyleClass().remove("none"); struc.get().getStyleClass().add("color-box"); if (val != null) { @@ -141,7 +149,7 @@ public class StoreSectionMiniComp extends Comp> { } }); } - }) + }) .createStructure(); } } diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSidebarComp.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSidebarComp.java index edccd1cf2..107e35e9b 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSidebarComp.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSidebarComp.java @@ -14,9 +14,18 @@ public class StoreSidebarComp extends SimpleComp { protected Region createSimple() { var sideBar = new VerticalComp(List.of( new StoreEntryListStatusComp().styleClass("color-box").styleClass("gray"), - new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()).styleClass("color-box").styleClass("gray"), - new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()).styleClass("color-box").styleClass("gray"), - Comp.of(() -> new Region()).styleClass("bar").styleClass("color-box").styleClass("gray").styleClass("filler-bar").vgrow())); + new StoreCategoryListComp(StoreViewState.get().getAllConnectionsCategory()) + .styleClass("color-box") + .styleClass("gray"), + new StoreCategoryListComp(StoreViewState.get().getAllScriptsCategory()) + .styleClass("color-box") + .styleClass("gray"), + Comp.of(() -> new Region()) + .styleClass("bar") + .styleClass("color-box") + .styleClass("gray") + .styleClass("filler-bar") + .vgrow())); sideBar.apply(struc -> struc.get().setFillWidth(true)); sideBar.styleClass("sidebar"); sideBar.prefWidth(240); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreSortMode.java b/app/src/main/java/io/xpipe/app/comp/store/StoreSortMode.java index 0a9bb031e..d5a7c81c9 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreSortMode.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreSortMode.java @@ -11,8 +11,6 @@ import java.util.stream.Stream; public interface StoreSortMode { - StoreSection representative(StoreSection s); - StoreSortMode ALPHABETICAL_DESC = new StoreSortMode() { @Override public StoreSection representative(StoreSection s) { @@ -30,7 +28,6 @@ public interface StoreSortMode { e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT)); } }; - StoreSortMode ALPHABETICAL_ASC = new StoreSortMode() { @Override public StoreSection representative(StoreSection s) { @@ -49,14 +46,19 @@ public interface StoreSortMode { .reversed(); } }; - StoreSortMode DATE_DESC = new StoreSortMode() { @Override public StoreSection representative(StoreSection s) { var c = comparator(); - return Stream.of(s.getShownChildren().stream().max((o1, o2) -> { - return c.compare(representative(o1), representative(o2)); - }).orElse(s), s).max(c).orElseThrow(); + return Stream.of( + s.getShownChildren().stream() + .max((o1, o2) -> { + return c.compare(representative(o1), representative(o2)); + }) + .orElse(s), + s) + .max(c) + .orElseThrow(); } @Override @@ -74,14 +76,19 @@ public interface StoreSortMode { }); } }; - StoreSortMode DATE_ASC = new StoreSortMode() { @Override public StoreSection representative(StoreSection s) { var c = comparator(); - return Stream.of(s.getShownChildren().stream().min((o1, o2) -> { - return c.compare(representative(o1), representative(o2)); - }).orElse(s), s).min(c).orElseThrow(); + return Stream.of( + s.getShownChildren().stream() + .min((o1, o2) -> { + return c.compare(representative(o1), representative(o2)); + }) + .orElse(s), + s) + .min(c) + .orElseThrow(); } @Override @@ -92,13 +99,15 @@ public interface StoreSortMode { @Override public Comparator comparator() { return Comparator.comparing(e -> { - return flatten(e) - .map(entry -> entry.getLastAccess()) - .max(Comparator.naturalOrder()) - .orElseThrow(); - }).reversed(); + return flatten(e) + .map(entry -> entry.getLastAccess()) + .max(Comparator.naturalOrder()) + .orElseThrow(); + }) + .reversed(); } }; + List ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC); static Stream flatten(StoreSection section) { return Stream.concat( @@ -106,14 +115,14 @@ public interface StoreSortMode { section.getAllChildren().stream().flatMap(section1 -> flatten(section1))); } - List ALL = List.of(ALPHABETICAL_DESC, ALPHABETICAL_ASC, DATE_DESC, DATE_ASC); - static Optional fromId(String id) { return ALL.stream() .filter(storeSortMode -> storeSortMode.getId().equals(id)) .findFirst(); } + StoreSection representative(StoreSection s); + String getId(); Comparator comparator(); diff --git a/app/src/main/java/io/xpipe/app/comp/store/StoreViewState.java b/app/src/main/java/io/xpipe/app/comp/store/StoreViewState.java index a4b2b5579..0a39d60f8 100644 --- a/app/src/main/java/io/xpipe/app/comp/store/StoreViewState.java +++ b/app/src/main/java/io/xpipe/app/comp/store/StoreViewState.java @@ -24,6 +24,22 @@ import java.util.stream.Collectors; public class StoreViewState { private static StoreViewState INSTANCE; + private final StringProperty filter = new SimpleStringProperty(); + @Getter + private final ObservableList allEntries = + FXCollections.observableList(new CopyOnWriteArrayList<>()); + @Getter + private final ObservableList categories = + FXCollections.observableList(new CopyOnWriteArrayList<>()); + @Getter + private final Property activeCategory = new SimpleObjectProperty<>(); + @Getter + private StoreSection currentTopLevelSection; + + private StoreViewState() { + initContent(); + addListeners(); + } public static void init() { if (INSTANCE != null) { @@ -53,27 +69,6 @@ public class StoreViewState { return INSTANCE; } - private final StringProperty filter = new SimpleStringProperty(); - - @Getter - private final ObservableList allEntries = - FXCollections.observableList(new CopyOnWriteArrayList<>()); - - @Getter - private final ObservableList categories = - FXCollections.observableList(new CopyOnWriteArrayList<>()); - - @Getter - private StoreSection currentTopLevelSection; - - @Getter - private final Property activeCategory = new SimpleObjectProperty<>(); - - private StoreViewState() { - initContent(); - addListeners(); - } - private void updateContent() { categories.forEach(c -> c.update()); allEntries.forEach(e -> e.update()); @@ -126,12 +121,14 @@ public class StoreViewState { }); } - // Watch out for synchronizing all calls to the entries and categories list! DataStorage.get().addListener(new StorageListener() { @Override public void onStoreAdd(DataStoreEntry... entry) { - var l = Arrays.stream(entry).map(StoreEntryWrapper::new).peek(storeEntryWrapper -> storeEntryWrapper.update()).toList(); + var l = Arrays.stream(entry) + .map(StoreEntryWrapper::new) + .peek(storeEntryWrapper -> storeEntryWrapper.update()) + .toList(); Platform.runLater(() -> { // Don't update anything if we have already reset if (INSTANCE == null) { diff --git a/app/src/main/java/io/xpipe/app/core/App.java b/app/src/main/java/io/xpipe/app/core/App.java index de3a64000..49e419a49 100644 --- a/app/src/main/java/io/xpipe/app/core/App.java +++ b/app/src/main/java/io/xpipe/app/core/App.java @@ -42,7 +42,8 @@ public class App extends Application { if (OsType.getLocal().equals(OsType.LINUX)) { try { Toolkit xToolkit = Toolkit.getDefaultToolkit(); - java.lang.reflect.Field awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName"); + java.lang.reflect.Field awtAppClassNameField = + xToolkit.getClass().getDeclaredField("awtAppClassName"); awtAppClassNameField.setAccessible(true); awtAppClassNameField.set(xToolkit, "XPipe"); } catch (Exception e) { @@ -92,5 +93,4 @@ public class App extends Application { stage.requestFocus(); }); } - } diff --git a/app/src/main/java/io/xpipe/app/core/AppBundledFonts.java b/app/src/main/java/io/xpipe/app/core/AppBundledFonts.java index 92c77e339..6f1381db2 100644 --- a/app/src/main/java/io/xpipe/app/core/AppBundledFonts.java +++ b/app/src/main/java/io/xpipe/app/core/AppBundledFonts.java @@ -16,7 +16,8 @@ public class AppBundledFonts { return; } - System.setProperty("prism.fontdir", XPipeInstallation.getBundledFontsPath().toString()); + System.setProperty( + "prism.fontdir", XPipeInstallation.getBundledFontsPath().toString()); System.setProperty("prism.embeddedfonts", "true"); } diff --git a/app/src/main/java/io/xpipe/app/core/AppDebugModeNotice.java b/app/src/main/java/io/xpipe/app/core/AppDebugModeNotice.java index 239cf1a3d..a005a3179 100644 --- a/app/src/main/java/io/xpipe/app/core/AppDebugModeNotice.java +++ b/app/src/main/java/io/xpipe/app/core/AppDebugModeNotice.java @@ -11,8 +11,9 @@ public class AppDebugModeNotice { } var out = AppLogs.get().getOriginalSysOut(); - var msg = """ - + var msg = + """ + **************************************** * You are running XPipe in debug mode! * * The debug console output can contain * diff --git a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java index dcdadc0e1..a1b009b0e 100644 --- a/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java +++ b/app/src/main/java/io/xpipe/app/core/AppExtensionManager.java @@ -28,6 +28,7 @@ public class AppExtensionManager { private final List leafModuleLayers = new ArrayList<>(); private final List extensionBaseDirectories = new ArrayList<>(); private ModuleLayer baseLayer = ModuleLayer.boot(); + @Getter private ModuleLayer extendedLayer; @@ -51,11 +52,20 @@ public class AppExtensionManager { XPipeServiceProviders.load(INSTANCE.extendedLayer); MessageExchangeImpls.loadAll(); } catch (Throwable t) { - throw new ExtensionException("Service provider initialization failed. Is the installation data corrupt?", t); + throw new ExtensionException( + "Service provider initialization failed. Is the installation data corrupt?", t); } } } + public static void reset() { + INSTANCE = null; + } + + public static AppExtensionManager getInstance() { + return INSTANCE; + } + private void loadBaseExtension() { var baseModule = findAndParseExtension("base", ModuleLayer.boot()); if (baseModule.isEmpty()) { @@ -94,14 +104,6 @@ public class AppExtensionManager { extensionBaseDirectories.add(productionRoot); } - public static void reset() { - INSTANCE = null; - } - - public static AppExtensionManager getInstance() { - return INSTANCE; - } - public Set getContentModules() { return Stream.concat( Stream.of(ModuleLayer.boot().findModule("io.xpipe.app").orElseThrow()), @@ -111,7 +113,8 @@ public class AppExtensionManager { private void loadAllExtensions() { for (var ext : List.of("jdbc", "proc", "uacc")) { - var extension = findAndParseExtension(ext,baseLayer).orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext)); + var extension = findAndParseExtension(ext, baseLayer) + .orElseThrow(() -> ExtensionException.corrupt("Missing module " + ext)); loadedExtensions.add(extension); leafModuleLayers.add(extension.getModule().getLayer()); } diff --git a/app/src/main/java/io/xpipe/app/core/AppFileWatcher.java b/app/src/main/java/io/xpipe/app/core/AppFileWatcher.java index b3bac3a79..dc8e1cfaf 100644 --- a/app/src/main/java/io/xpipe/app/core/AppFileWatcher.java +++ b/app/src/main/java/io/xpipe/app/core/AppFileWatcher.java @@ -107,6 +107,7 @@ public class AppFileWatcher { private class WatchedDirectory { private final BiConsumer> listener; + @Getter private final Path baseDir; @@ -114,9 +115,7 @@ public class AppFileWatcher { this.baseDir = dir; this.listener = listener; createRecursiveWatchers(dir); - TrackEvent.withTrace("Added watched directory") - .tag("location", dir) - .handle(); + TrackEvent.withTrace("Added watched directory").tag("location", dir).handle(); } private void createRecursiveWatchers(Path dir) { @@ -184,6 +183,5 @@ public class AppFileWatcher { .handle(); listener.accept(file, ev.kind()); } - } } diff --git a/app/src/main/java/io/xpipe/app/core/AppFont.java b/app/src/main/java/io/xpipe/app/core/AppFont.java index f14aba45c..2ad6e05b1 100644 --- a/app/src/main/java/io/xpipe/app/core/AppFont.java +++ b/app/src/main/java/io/xpipe/app/core/AppFont.java @@ -54,7 +54,8 @@ public class AppFont { try (var in = Files.newInputStream(file)) { Font.loadFont(in, OsType.getLocal() == OsType.LINUX ? 11 : 12); } catch (Throwable t) { - // Font loading can fail in rare cases. This is however not important, so we can just ignore it + // Font loading can fail in rare cases. This is however not important, so we can just ignore + // it } return FileVisitResult.CONTINUE; } diff --git a/app/src/main/java/io/xpipe/app/core/AppI18n.java b/app/src/main/java/io/xpipe/app/core/AppI18n.java index e2c437ff5..327042b30 100644 --- a/app/src/main/java/io/xpipe/app/core/AppI18n.java +++ b/app/src/main/java/io/xpipe/app/core/AppI18n.java @@ -37,10 +37,10 @@ import java.util.regex.Pattern; public class AppI18n { private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$"); + private static final AppI18n INSTANCE = new AppI18n(); private Map translations; private Map markdownDocumentations; private PrettyTime prettyTime; - private static final AppI18n INSTANCE = new AppI18n(); public static void init() { var i = INSTANCE; @@ -98,8 +98,11 @@ public class AppI18n { return "null"; } - return getInstance().prettyTime.formatDuration( - getInstance().prettyTime.approximateDuration(Instant.now().plus(duration.getValue()))); + return getInstance() + .prettyTime + .formatDuration(getInstance() + .prettyTime + .approximateDuration(Instant.now().plus(duration.getValue()))); }, duration); } @@ -136,20 +139,6 @@ public class AppI18n { return s; } - private void clear() { - translations.clear(); - prettyTime = null; - } - - @SuppressWarnings("removal") - public static class CallingClass extends SecurityManager { - public static final CallingClass INSTANCE = new CallingClass(); - - public Class[] getCallingClasses() { - return getClassContext(); - } - } - @SneakyThrows private static String getCallerModuleName() { var callers = CallingClass.INSTANCE.getCallingClasses(); @@ -171,6 +160,11 @@ public class AppI18n { return ""; } + private void clear() { + translations.clear(); + prettyTime = null; + } + public String getKey(String s) { var key = s; if (!s.contains(".")) { @@ -220,7 +214,8 @@ public class AppI18n { public String getMarkdownDocumentation(String name) { if (!markdownDocumentations.containsKey(name)) { - TrackEvent.withWarn("Markdown documentation for key " + name + " not found").handle(); + TrackEvent.withWarn("Markdown documentation for key " + name + " not found") + .handle(); } return markdownDocumentations.getOrDefault(name, ""); @@ -315,4 +310,13 @@ public class AppI18n { ? AppPrefs.get().language().getValue().getLocale() : SupportedLocale.ENGLISH.getLocale()); } + + @SuppressWarnings("removal") + public static class CallingClass extends SecurityManager { + public static final CallingClass INSTANCE = new CallingClass(); + + public Class[] getCallingClasses() { + return getClassContext(); + } + } } diff --git a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java index 96dec53f9..8ec55cdec 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java +++ b/app/src/main/java/io/xpipe/app/core/AppLayoutModel.java @@ -21,16 +21,20 @@ import java.util.List; public class AppLayoutModel { - @Data - @Builder - @Jacksonized - public static class SavedState { - - double sidebarWidth; - double browserConnectionsWidth; - } - private static AppLayoutModel INSTANCE; + @Getter + private final SavedState savedState; + @Getter + private final List entries; + private final Property selected; + private final ObservableValue selectedWrapper; + + public AppLayoutModel(SavedState savedState) { + this.savedState = savedState; + this.entries = createEntryList(); + this.selected = new SimpleObjectProperty<>(entries.get(1)); + this.selectedWrapper = PlatformThread.sync(selected); + } public static AppLayoutModel get() { return INSTANCE; @@ -46,20 +50,6 @@ public class AppLayoutModel { INSTANCE = null; } - @Getter - private final SavedState savedState; - @Getter - private final List entries; - private final Property selected; - private final ObservableValue selectedWrapper; - - public AppLayoutModel(SavedState savedState) { - this.savedState = savedState; - this.entries = createEntryList(); - this.selected = new SimpleObjectProperty<>(entries.get(1)); - this.selectedWrapper = PlatformThread.sync(selected); - } - public Property getSelectedInternal() { return selected; } @@ -86,17 +76,14 @@ public class AppLayoutModel { private List createEntryList() { var l = new ArrayList<>(List.of( - new Entry( - AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)), + new Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new BrowserComp(BrowserModel.DEFAULT)), new Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()), - new Entry( - AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp()))); + new Entry(AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new AppPrefsComp()))); // new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new // StorageLayoutComp()), // new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp()) if (AppProperties.get().isDeveloperMode() && !AppProperties.get().isImage()) { - l.add(new Entry( - AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp())); + l.add(new Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp())); } l.add(new Entry( @@ -107,5 +94,14 @@ public class AppLayoutModel { return l; } + @Data + @Builder + @Jacksonized + public static class SavedState { + + double sidebarWidth; + double browserConnectionsWidth; + } + public record Entry(ObservableValue name, String icon, Comp comp) {} } diff --git a/app/src/main/java/io/xpipe/app/core/AppLogs.java b/app/src/main/java/io/xpipe/app/core/AppLogs.java index 88e7a3043..0973f32b4 100644 --- a/app/src/main/java/io/xpipe/app/core/AppLogs.java +++ b/app/src/main/java/io/xpipe/app/core/AppLogs.java @@ -44,10 +44,13 @@ public class AppLogs { DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(ZoneId.systemDefault()); private static AppLogs INSTANCE; + @Getter private final PrintStream originalSysOut; + @Getter private final PrintStream originalSysErr; + private final Path logDir; @Getter @@ -61,7 +64,8 @@ public class AppLogs { private final PrintStream outFileStream; - public AppLogs(Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel, PrintStream outFileStream) { + public AppLogs( + Path logDir, boolean writeToSysout, boolean writeToFile, String logLevel, PrintStream outFileStream) { this.logDir = logDir; this.writeToSysout = writeToSysout; this.writeToFile = writeToFile; @@ -171,6 +175,15 @@ public class AppLogs { return INSTANCE; } + private static String determineLogLevel() { + if (System.getProperty(LOG_LEVEL_PROP) != null) { + String p = System.getProperty(LOG_LEVEL_PROP); + return LOG_LEVELS.contains(p) ? p : "trace"; + } + + return DEFAULT_LOG_LEVEL; + } + private void close() { if (outFileStream != null) { outFileStream.close(); @@ -197,11 +210,7 @@ public class AppLogs { return; } - TrackEvent.builder() - .type("info") - .message(line) - .build() - .handle(); + TrackEvent.builder().type("info").message(line).build().handle(); baos.reset(); } else { baos.write(b); @@ -231,15 +240,6 @@ public class AppLogs { })); } - private static String determineLogLevel() { - if (System.getProperty(LOG_LEVEL_PROP) != null) { - String p = System.getProperty(LOG_LEVEL_PROP); - return LOG_LEVELS.contains(p) ? p : "trace"; - } - - return DEFAULT_LOG_LEVEL; - } - public void logException(String description, Throwable e) { var deob = Deobfuscator.deobfuscateToString(e); var event = TrackEvent.builder() diff --git a/app/src/main/java/io/xpipe/app/core/AppProperties.java b/app/src/main/java/io/xpipe/app/core/AppProperties.java index 0506ad7ba..e5aac67d7 100644 --- a/app/src/main/java/io/xpipe/app/core/AppProperties.java +++ b/app/src/main/java/io/xpipe/app/core/AppProperties.java @@ -19,15 +19,20 @@ public class AppProperties { private static final String EXTENSION_PATHS_PROP = "io.xpipe.app.extensions"; private static AppProperties INSTANCE; boolean fullVersion; + @Getter String version; + @Getter String build; + UUID buildUuid; String sentryUrl; String arch; + @Getter boolean image; + boolean staging; boolean useVirtualThreads; boolean debugThreads; @@ -102,8 +107,7 @@ public class AppProperties { } public boolean isDevelopmentEnvironment() { - return !AppProperties.get().isImage() - && AppProperties.get().isDeveloperMode(); + return !AppProperties.get().isImage() && AppProperties.get().isDeveloperMode(); } public boolean isDeveloperMode() { @@ -113,5 +117,4 @@ public class AppProperties { return AppPrefs.get().developerMode().getValue(); } - } diff --git a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java index 1b11a3c5f..8a084605c 100644 --- a/app/src/main/java/io/xpipe/app/core/AppSocketServer.java +++ b/app/src/main/java/io/xpipe/app/core/AppSocketServer.java @@ -57,7 +57,10 @@ public class AppSocketServer { .handle(); } catch (Exception ex) { // Not terminal! - ErrorEvent.fromThrowable(ex).description("Unable to start local socket server on port " + port).build().handle(); + ErrorEvent.fromThrowable(ex) + .description("Unable to start local socket server on port " + port) + .build() + .handle(); } } @@ -246,9 +249,7 @@ public class AppSocketServer { } } - TrackEvent.builder() - .type("trace") - .message("Socket connection #" + id + " finished unsuccessfully"); + TrackEvent.builder().type("trace").message("Socket connection #" + id + " finished unsuccessfully"); } private void performExchangesAsync(Socket clientSocket) { diff --git a/app/src/main/java/io/xpipe/app/core/AppState.java b/app/src/main/java/io/xpipe/app/core/AppState.java index bd479ab58..9c830f8ac 100644 --- a/app/src/main/java/io/xpipe/app/core/AppState.java +++ b/app/src/main/java/io/xpipe/app/core/AppState.java @@ -15,8 +15,9 @@ public class AppState { boolean initialLaunch; @NonFinal - @Setter + @Setter String userName; + @NonFinal @Setter String userEmail; diff --git a/app/src/main/java/io/xpipe/app/core/AppStyle.java b/app/src/main/java/io/xpipe/app/core/AppStyle.java index f926bd055..30f69d9f5 100644 --- a/app/src/main/java/io/xpipe/app/core/AppStyle.java +++ b/app/src/main/java/io/xpipe/app/core/AppStyle.java @@ -55,10 +55,12 @@ public class AppStyle { try { var bytes = Files.readAllBytes(file); if (file.getFileName().toString().endsWith(".bss")) { - var s = "data:application/octet-stream;base64," + Base64.getEncoder().encodeToString(bytes); + var s = "data:application/octet-stream;base64," + + Base64.getEncoder().encodeToString(bytes); STYLESHEET_CONTENTS.put(file, s); } else if (file.getFileName().toString().endsWith(".css")) { - var s = "data:text/css;base64," + Base64.getEncoder().encodeToString(bytes); + var s = "data:text/css;base64," + + Base64.getEncoder().encodeToString(bytes); STYLESHEET_CONTENTS.put(file, s); } } catch (IOException ex) { diff --git a/app/src/main/java/io/xpipe/app/core/AppTheme.java b/app/src/main/java/io/xpipe/app/core/AppTheme.java index 6fa98a4dd..967b46cef 100644 --- a/app/src/main/java/io/xpipe/app/core/AppTheme.java +++ b/app/src/main/java/io/xpipe/app/core/AppTheme.java @@ -79,11 +79,13 @@ public class AppTheme { Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> { Platform.runLater(() -> { - if (t1 == ColorScheme.DARK && !AppPrefs.get().theme.getValue().isDark()) { + if (t1 == ColorScheme.DARK + && !AppPrefs.get().theme.getValue().isDark()) { AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme()); } - if (t1 != ColorScheme.DARK && AppPrefs.get().theme.getValue().isDark()) { + if (t1 != ColorScheme.DARK + && AppPrefs.get().theme.getValue().isDark()) { AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme()); } }); @@ -204,6 +206,10 @@ public class AppTheme { // Also include your custom theme here public static final List ALL = List.of(PRIMER_LIGHT, PRIMER_DARK, NORD_LIGHT, NORD_DARK, CUPERTINO_LIGHT, CUPERTINO_DARK, DRACULA); + protected final String id; + @Getter + protected final String cssId; + protected final atlantafx.base.theme.Theme theme; static Theme getDefaultLightTheme() { return switch (OsType.getLocal()) { @@ -221,13 +227,6 @@ public class AppTheme { }; } - protected final String id; - - @Getter - protected final String cssId; - - protected final atlantafx.base.theme.Theme theme; - public boolean isDark() { return theme.isDarkMode(); } diff --git a/app/src/main/java/io/xpipe/app/core/AppTray.java b/app/src/main/java/io/xpipe/app/core/AppTray.java index 82d0cbd3b..ac117a4dd 100644 --- a/app/src/main/java/io/xpipe/app/core/AppTray.java +++ b/app/src/main/java/io/xpipe/app/core/AppTray.java @@ -14,6 +14,7 @@ public class AppTray { private static AppTray INSTANCE; private final AppTrayIcon icon; + @Getter private final ErrorHandler errorHandler; diff --git a/app/src/main/java/io/xpipe/app/core/AppTrayIcon.java b/app/src/main/java/io/xpipe/app/core/AppTrayIcon.java index 51a747d08..e447772ba 100644 --- a/app/src/main/java/io/xpipe/app/core/AppTrayIcon.java +++ b/app/src/main/java/io/xpipe/app/core/AppTrayIcon.java @@ -20,15 +20,17 @@ public class AppTrayIcon { tray = SystemTray.getSystemTray(); - var image = switch (OsType.getLocal()) { - case OsType.Windows windows -> "img/logo/logo_16x16.png"; - case OsType.Linux linux -> "img/logo/logo_24x24.png"; - case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png"; - }; + var image = + switch (OsType.getLocal()) { + case OsType.Windows windows -> "img/logo/logo_16x16.png"; + case OsType.Linux linux -> "img/logo/logo_24x24.png"; + case OsType.MacOs macOs -> "img/logo/logo_macos_tray_24x24.png"; + }; var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, image).orElseThrow(); PopupMenu popupMenu = new PopupMenu(); - this.trayIcon = new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu); + this.trayIcon = + new TrayIcon(loadImageFromURL(url), App.getApp().getStage().getTitle(), popupMenu); this.trayIcon.setToolTip("XPipe"); this.trayIcon.setImageAutoSize(true); @@ -58,6 +60,19 @@ public class AppTrayIcon { }); } + private static Image loadImageFromURL(URL iconImagePath) { + try { + return ImageIO.read(iconImagePath); + } catch (IOException e) { + ErrorEvent.fromThrowable(e).handle(); + return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE); + } + } + + public static boolean isSupported() { + return Desktop.isDesktopSupported() && SystemTray.isSupported(); + } + public final TrayIcon getAwtTrayIcon() { return trayIcon; } @@ -65,17 +80,7 @@ public class AppTrayIcon { private void ensureSystemTraySupported() { if (!SystemTray.isSupported()) { throw new UnsupportedOperationException( - "SystemTray icons are not " - + "supported by the current desktop environment."); - } - } - - private static Image loadImageFromURL(URL iconImagePath) { - try { - return ImageIO.read(iconImagePath); - } catch (IOException e) { - ErrorEvent.fromThrowable(e).handle(); - return AppImages.toAwtImage(AppImages.DEFAULT_IMAGE); + "SystemTray icons are not " + "supported by the current desktop environment."); } } @@ -129,11 +134,9 @@ public class AppTrayIcon { public void showInfoMessage(String title, String message) { if (OsType.getLocal().equals(OsType.MACOS)) { - showMacAlert(title, message,"Information"); + showMacAlert(title, message, "Information"); } else { - EventQueue.invokeLater(() -> - this.trayIcon.displayMessage( - title, message, TrayIcon.MessageType.INFO)); + EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.INFO)); } } @@ -143,11 +146,9 @@ public class AppTrayIcon { public void showWarningMessage(String title, String message) { if (OsType.getLocal().equals(OsType.MACOS)) { - showMacAlert(title, message,"Warning"); + showMacAlert(title, message, "Warning"); } else { - EventQueue.invokeLater(() -> - this.trayIcon.displayMessage( - title, message, TrayIcon.MessageType.WARNING)); + EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.WARNING)); } } @@ -157,11 +158,9 @@ public class AppTrayIcon { public void showErrorMessage(String title, String message) { if (OsType.getLocal().equals(OsType.MACOS)) { - showMacAlert(title, message,"Error"); + showMacAlert(title, message, "Error"); } else { - EventQueue.invokeLater(() -> - this.trayIcon.displayMessage( - title, message, TrayIcon.MessageType.ERROR)); + EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.ERROR)); } } @@ -171,11 +170,9 @@ public class AppTrayIcon { public void showMessage(String title, String message) { if (OsType.getLocal().equals(OsType.MACOS)) { - showMacAlert(title, message,"Message"); + showMacAlert(title, message, "Message"); } else { - EventQueue.invokeLater(() -> - this.trayIcon.displayMessage( - title, message, TrayIcon.MessageType.NONE)); + EventQueue.invokeLater(() -> this.trayIcon.displayMessage(title, message, TrayIcon.MessageType.NONE)); } } @@ -183,26 +180,14 @@ public class AppTrayIcon { this.showMessage(null, message); } - public static boolean isSupported() { - return Desktop.isDesktopSupported() && SystemTray.isSupported(); - } - private void showMacAlert(String subTitle, String message, String title) { String execute = String.format( - "display notification \"%s\"" - + " with title \"%s\"" - + " subtitle \"%s\"", - message != null ? message : "", - title != null ? title : "", - subTitle != null ? subTitle : "" - ); + "display notification \"%s\"" + " with title \"%s\"" + " subtitle \"%s\"", + message != null ? message : "", title != null ? title : "", subTitle != null ? subTitle : ""); try { - Runtime.getRuntime() - .exec(new String[] { "osascript", "-e", execute }); + Runtime.getRuntime().exec(new String[] {"osascript", "-e", execute}); } catch (IOException e) { - throw new UnsupportedOperationException( - "Cannot run osascript with given parameters."); + throw new UnsupportedOperationException("Cannot run osascript with given parameters."); } } } - diff --git a/app/src/main/java/io/xpipe/app/core/AppWindowHelper.java b/app/src/main/java/io/xpipe/app/core/AppWindowHelper.java index eb7259926..15d961a74 100644 --- a/app/src/main/java/io/xpipe/app/core/AppWindowHelper.java +++ b/app/src/main/java/io/xpipe/app/core/AppWindowHelper.java @@ -109,8 +109,7 @@ public class AppWindowHelper { childStage.setY(stage.getY() + stage.getHeight() / 2 - childStage.getHeight() / 2); } - public static void showAlert( - Consumer c, Consumer> bt) { + public static void showAlert(Consumer c, Consumer> bt) { ThreadHelper.runAsync(() -> { var r = showBlockingAlert(c); if (bt != null) { @@ -137,7 +136,8 @@ public class AppWindowHelper { .orElse(false); } - public static boolean showConfirmationAlert(ObservableValue title, ObservableValue header, ObservableValue content) { + public static boolean showConfirmationAlert( + ObservableValue title, ObservableValue header, ObservableValue content) { return AppWindowHelper.showBlockingAlert(alert -> { alert.titleProperty().bind(title); alert.headerTextProperty().bind(header); @@ -273,7 +273,11 @@ public class AppWindowHelper { } var allScreenBounds = computeWindowScreenBounds(stage); - if (!areNumbersValid(allScreenBounds.getMinX(), allScreenBounds.getMinY(), allScreenBounds.getMaxX(), allScreenBounds.getMaxY())) { + if (!areNumbersValid( + allScreenBounds.getMinX(), + allScreenBounds.getMinY(), + allScreenBounds.getMaxX(), + allScreenBounds.getMaxY())) { return Optional.empty(); } @@ -324,43 +328,46 @@ public class AppWindowHelper { private static List getWindowScreens(Stage stage) { if (!areNumbersValid(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())) { - return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage ? getWindowScreens(ownerStage) : List.of(Screen.getPrimary()); + return stage.getOwner() != null && stage.getOwner() instanceof Stage ownerStage + ? getWindowScreens(ownerStage) + : List.of(Screen.getPrimary()); } - return Screen.getScreensForRectangle(new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())); + return Screen.getScreensForRectangle( + new Rectangle2D(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight())); } private static Rectangle2D computeWindowScreenBounds(Stage stage) { - double minX = Double.POSITIVE_INFINITY ; - double minY = Double.POSITIVE_INFINITY ; - double maxX = Double.NEGATIVE_INFINITY ; - double maxY = Double.NEGATIVE_INFINITY ; + double minX = Double.POSITIVE_INFINITY; + double minY = Double.POSITIVE_INFINITY; + double maxX = Double.NEGATIVE_INFINITY; + double maxY = Double.NEGATIVE_INFINITY; for (Screen screen : getWindowScreens(stage)) { Rectangle2D screenBounds = screen.getBounds(); if (screenBounds.getMinX() < minX) { minX = screenBounds.getMinX(); } if (screenBounds.getMinY() < minY) { - minY = screenBounds.getMinY() ; + minY = screenBounds.getMinY(); } if (screenBounds.getMaxX() > maxX) { maxX = screenBounds.getMaxX(); } if (screenBounds.getMaxY() > maxY) { - maxY = screenBounds.getMaxY() ; + maxY = screenBounds.getMaxY(); } } // Taskbar adjustment maxY -= 50; - var w = maxX-minX; - var h = maxY-minY; + var w = maxX - minX; + var h = maxY - minY; // This should not happen but on weird Linux systems nothing is impossible if (w < 0 || h < 0) { - return new Rectangle2D(0,0,800, 600); + return new Rectangle2D(0, 0, 800, 600); } - + return new Rectangle2D(minX, minY, w, h); } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java index e272d7b32..b90b314d5 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppAvCheck.java @@ -17,54 +17,6 @@ import java.util.Optional; public class AppAvCheck { - @Getter - public enum AvType { - - BITDEFENDER("Bitdefender") { - @Override - public String getDescription() { - return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable."; - } - - @Override - public boolean isActive() { - return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Bitdefender", "InstallDir"); - } - }, - MALWAREBYTES("Malwarebytes") { - @Override - public String getDescription() { - return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow."; - } - - @Override - public boolean isActive() { - return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\Malwarebytes", "id"); - } - }, - MCAFEE("McAfee") { - @Override - public String getDescription() { - return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors."; - } - - @Override - public boolean isActive() { - return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE,"SOFTWARE\\McAfee", "mi"); - } - }; - - private final String name; - - AvType(String name) { - this.name = name; - } - - public abstract String getDescription(); - - public abstract boolean isActive(); - } - private static Optional detect() { for (AvType value : AvType.values()) { if (value.isActive()) { @@ -93,21 +45,75 @@ public class AppAvCheck { alert.setTitle(AppI18n.get("antivirusNoticeTitle")); alert.setAlertType(Alert.AlertType.NONE); - AppResources.with( - AppResources.XPIPE_MODULE, - "misc/antivirus.md", - file -> { - var markdown = new MarkdownComp(Files.readString(file), s -> { + AppResources.with(AppResources.XPIPE_MODULE, "misc/antivirus.md", file -> { + var markdown = new MarkdownComp(Files.readString(file), s -> { var t = found.get(); - return s.formatted(t.getName(), t.getName(), t.getDescription(), AppProperties.get().getVersion(), AppProperties.get().getVersion(), t.getName()); - }).prefWidth(550).prefHeight(600).createRegion(); - alert.getDialogPane().setContent(markdown); - alert.getDialogPane().setPadding(new Insets(15)); - }); + return s.formatted( + t.getName(), + t.getName(), + t.getDescription(), + AppProperties.get().getVersion(), + AppProperties.get().getVersion(), + t.getName()); + }) + .prefWidth(550) + .prefHeight(600) + .createRegion(); + alert.getDialogPane().setContent(markdown); + alert.getDialogPane().setPadding(new Insets(15)); + }); alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE)); }); a.filter(b -> b.getButtonData().isDefaultButton()) .ifPresentOrElse(buttonType -> {}, () -> OperationMode.halt(1)); } + + @Getter + public enum AvType { + BITDEFENDER("Bitdefender") { + @Override + public String getDescription() { + return "Bitdefender sometimes isolates XPipe and some shell programs, effectively making it unusable."; + } + + @Override + public boolean isActive() { + return WindowsRegistry.exists( + WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Bitdefender", "InstallDir"); + } + }, + MALWAREBYTES("Malwarebytes") { + @Override + public String getDescription() { + return "The free Malwarebytes version performs less invasive scans, so it shouldn't be a problem. If you are running the paid Malwarebytes Pro version, you will have access to the `Exploit Protection` under the `Real-time Protection` mode. When this setting is active, any shell access is slowed down, resulting in XPipe becoming very slow."; + } + + @Override + public boolean isActive() { + return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\Malwarebytes", "id"); + } + }, + MCAFEE("McAfee") { + @Override + public String getDescription() { + return "McAfee slows down XPipe considerably. It also sometimes preemptively disables some Win32 commands that XPipe depends on, leading to errors."; + } + + @Override + public boolean isActive() { + return WindowsRegistry.exists(WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\McAfee", "mi"); + } + }; + + private final String name; + + AvType(String name) { + this.name = name; + } + + public abstract String getDescription(); + + public abstract boolean isActive(); + } } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppCertutilCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppCertutilCheck.java index 6f4937a75..c5d6c6256 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppCertutilCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppCertutilCheck.java @@ -8,7 +8,8 @@ import java.util.concurrent.TimeUnit; public class AppCertutilCheck { private static boolean getResult() { - var fc = new ProcessBuilder(System.getenv("WINDIR") + "\\Windows32\\certutil").redirectError(ProcessBuilder.Redirect.DISCARD); + var fc = new ProcessBuilder(System.getenv("WINDIR") + "\\Windows32\\certutil") + .redirectError(ProcessBuilder.Redirect.DISCARD); try { var proc = fc.start(); var out = new String(proc.getInputStream().readAllBytes()); diff --git a/app/src/main/java/io/xpipe/app/core/check/AppShellCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppShellCheck.java index f7b8fa66c..2ae0086ac 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppShellCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppShellCheck.java @@ -12,19 +12,25 @@ public class AppShellCheck { public static void check() { var err = selfTestErrorCheck(); if (err.isPresent()) { - var msg = """ + var msg = + """ Shell self-test failed for %s: %s - + This indicates that something is seriously wrong and certain shell functionality will not work as expected. - + The most likely causes are: - On Windows, an AntiVirus program might block required programs and commands - The system shell is restricted or blocked - The operating system is not supported - + You can reach out to us if you want to properly diagnose the cause individually and hopefully fix it. - """.formatted(ProcessControlProvider.get().getEffectiveLocalDialect().getDisplayName(), err.get()); + """ + .formatted( + ProcessControlProvider.get() + .getEffectiveLocalDialect() + .getDisplayName(), + err.get()); ErrorEvent.fromThrowable(new IllegalStateException(msg)).handle(); } } diff --git a/app/src/main/java/io/xpipe/app/core/check/AppTempCheck.java b/app/src/main/java/io/xpipe/app/core/check/AppTempCheck.java index d0b7af27f..33c3d4551 100644 --- a/app/src/main/java/io/xpipe/app/core/check/AppTempCheck.java +++ b/app/src/main/java/io/xpipe/app/core/check/AppTempCheck.java @@ -18,8 +18,8 @@ public class AppTempCheck { } if (dir == null || !Files.exists(dir) || !Files.isDirectory(dir)) { - ErrorEvent.fromThrowable( - new IOException("Specified temporary directory " + tmpdir + ", set via the environment variable %TEMP% is invalid.")) + ErrorEvent.fromThrowable(new IOException("Specified temporary directory " + tmpdir + + ", set via the environment variable %TEMP% is invalid.")) .term() .handle(); } diff --git a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java index f7372c07c..1cb70ac86 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/BaseMode.java @@ -11,7 +11,10 @@ import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.update.XPipeDistributionType; -import io.xpipe.app.util.*; +import io.xpipe.app.util.FileBridge; +import io.xpipe.app.util.LicenseProvider; +import io.xpipe.app.util.LocalShell; +import io.xpipe.app.util.UnlockAlert; import io.xpipe.core.util.JacksonMapper; public class BaseMode extends OperationMode { diff --git a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java index 49504b4de..ea144e23a 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/GuiMode.java @@ -16,6 +16,16 @@ public class GuiMode extends PlatformMode { return "gui"; } + @Override + public void onSwitchFrom() { + PlatformThread.runLaterIfNeededBlocking(() -> { + TrackEvent.info("Closing windows"); + Stage.getWindows().stream().toList().forEach(w -> { + w.hide(); + }); + }); + } + @Override public void onSwitchTo() throws Throwable { super.onSwitchTo(); @@ -37,14 +47,4 @@ public class GuiMode extends PlatformMode { UpdateChangelogAlert.showIfNeeded(); } - - @Override - public void onSwitchFrom() { - PlatformThread.runLaterIfNeededBlocking(() -> { - TrackEvent.info("Closing windows"); - Stage.getWindows().stream().toList().forEach(w -> { - w.hide(); - }); - }); - } } diff --git a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java index 7209ceb74..50abd66e7 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/OperationMode.java @@ -36,12 +36,16 @@ public abstract class OperationMode { public static final OperationMode GUI = new GuiMode(); private static final Pattern PROPERTY_PATTERN = Pattern.compile("^-[DP](.+)=(.+)$"); private static final List ALL = List.of(BACKGROUND, TRAY, GUI); + @Getter private static boolean inStartup; + @Getter private static boolean inShutdown; + @Getter private static boolean inShutdownHook; + private static OperationMode CURRENT = null; public static OperationMode map(XPipeDaemonMode mode) { @@ -137,8 +141,8 @@ public abstract class OperationMode { }); // Do it this way to prevent IDE inspections from complaining - var c = Class.forName(ModuleLayer.boot().findModule("java.desktop").orElseThrow(), - "com.apple.eawt.Application"); + var c = Class.forName( + ModuleLayer.boot().findModule("java.desktop").orElseThrow(), "com.apple.eawt.Application"); var m = c.getDeclaredMethod("addAppEventListener", SystemEventListener.class); m.invoke(c.getMethod("getApplication").invoke(null), new AppReopenedListener() { @Override @@ -170,19 +174,23 @@ public abstract class OperationMode { public static void switchToAsync(OperationMode newMode) { ThreadHelper.createPlatformThread("mode switcher", false, () -> { - switchToSyncIfPossible(newMode); - }).start(); + switchToSyncIfPossible(newMode); + }) + .start(); } public static void switchToSyncOrThrow(OperationMode newMode) throws Throwable { TrackEvent.info("Attempting to switch mode to " + newMode.getId()); if (!newMode.isSupported()) { - throw PlatformState.getLastError() != null ? PlatformState.getLastError() : new IllegalStateException("Unsupported operation mode: " + newMode.getId()); + throw PlatformState.getLastError() != null + ? PlatformState.getLastError() + : new IllegalStateException("Unsupported operation mode: " + newMode.getId()); } set(newMode); } + public static boolean switchToSyncIfPossible(OperationMode newMode) { TrackEvent.info("Attempting to switch mode to " + newMode.getId()); @@ -202,7 +210,6 @@ public abstract class OperationMode { return true; } - public static void switchUp(OperationMode newMode) { if (newMode == BACKGROUND) { return; @@ -231,7 +238,8 @@ public abstract class OperationMode { public static void restart() { OperationMode.executeAfterShutdown(() -> { - var exec = XPipeInstallation.createExternalAsyncLaunchCommand(XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, ""); + var exec = XPipeInstallation.createExternalAsyncLaunchCommand( + XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, ""); LocalShell.getShell().executeSimpleCommand(exec); }); } @@ -312,20 +320,20 @@ public abstract class OperationMode { OperationMode.halt(hasError ? 1 : 0); } -// public static synchronized void reload() { -// ThreadHelper.create("reloader", false, () -> { -// try { -// switchTo(BACKGROUND); -// CURRENT.finalTeardown(); -// CURRENT.onSwitchTo(); -// switchTo(GUI); -// } catch (Throwable t) { -// ErrorEvent.fromThrowable(t).build().handle(); -// OperationMode.halt(1); -// } -// }) -// .start(); -// } + // public static synchronized void reload() { + // ThreadHelper.create("reloader", false, () -> { + // try { + // switchTo(BACKGROUND); + // CURRENT.finalTeardown(); + // CURRENT.onSwitchTo(); + // switchTo(GUI); + // } catch (Throwable t) { + // ErrorEvent.fromThrowable(t).build().handle(); + // OperationMode.halt(1); + // } + // }) + // .start(); + // } private static synchronized void set(OperationMode newMode) { if (CURRENT == null && newMode == null) { diff --git a/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java b/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java index b3189d27f..83bed40c9 100644 --- a/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java +++ b/app/src/main/java/io/xpipe/app/core/mode/TrayMode.java @@ -11,12 +11,10 @@ public class TrayMode extends PlatformMode { @Override public boolean isSupported() { - return !OsType.getLocal().equals(OsType.MACOS) && super.isSupported() && Desktop.isDesktopSupported() && SystemTray.isSupported(); - } - - @Override - public String getId() { - return "tray"; + return !OsType.getLocal().equals(OsType.MACOS) + && super.isSupported() + && Desktop.isDesktopSupported() + && SystemTray.isSupported(); } @Override @@ -33,6 +31,11 @@ public class TrayMode extends PlatformMode { }); } + @Override + public String getId() { + return "tray"; + } + @Override public void onSwitchFrom() { if (AppTray.get() != null) { diff --git a/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java index 95a9dacc2..c91f47c6a 100644 --- a/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/AskpassExchangeImpl.java @@ -11,7 +11,9 @@ public class AskpassExchangeImpl extends AskpassExchange @Override public Response handleRequest(BeaconHandler handler, Request msg) { - var found = msg.getSecretId() != null ? SecretManager.getProgress(msg.getRequest(), msg.getSecretId()) : SecretManager.getProgress(msg.getRequest()); + var found = msg.getSecretId() != null + ? SecretManager.getProgress(msg.getRequest(), msg.getSecretId()) + : SecretManager.getProgress(msg.getRequest()); if (found.isEmpty()) { return Response.builder().build(); } @@ -21,6 +23,8 @@ public class AskpassExchangeImpl extends AskpassExchange var p = found.get(); var secret = p.process(msg.getPrompt()); - return Response.builder().value(secret != null ? secret.inPlace() : null).build(); + return Response.builder() + .value(secret != null ? secret.inPlace() : null) + .build(); } } diff --git a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java index fd719aae6..74082a05a 100644 --- a/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/LaunchExchangeImpl.java @@ -16,9 +16,8 @@ public class LaunchExchangeImpl extends LaunchExchange public Response handleRequest(BeaconHandler handler, Request msg) throws Exception { var store = getStoreEntryById(msg.getId(), false); if (store.getStore() instanceof LaunchableStore s) { - var command = s.prepareLaunchCommand().prepareTerminalOpen( - TerminalInitScriptConfig.ofName(store.getName()), - null); + var command = s.prepareLaunchCommand() + .prepareTerminalOpen(TerminalInitScriptConfig.ofName(store.getName()), null); return Response.builder().command(split(command)).build(); } @@ -28,9 +27,8 @@ public class LaunchExchangeImpl extends LaunchExchange private List split(String command) { var split = Arrays.stream(command.split(" ", 3)).collect(Collectors.toList()); var s = split.get(2); - if ((s.startsWith("\"") && s.endsWith("\"")) - || (s.startsWith("'") && s.endsWith("'"))) { - split.set(2,s.substring(1, s.length() - 1)); + if ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))) { + split.set(2, s.substring(1, s.length() - 1)); } return split; } diff --git a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java index 94c3cb845..4bf19c234 100644 --- a/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/MessageExchangeImpl.java @@ -34,8 +34,8 @@ public interface MessageExchangeImpl getName(category), category -> all.stream() - .filter(dataStoreProvider -> - category.equals(dataStoreProvider.getCreationCategory())) + .filter(dataStoreProvider -> category.equals(dataStoreProvider.getCreationCategory())) .map(p -> ProviderEntry.builder() .id(p.getId()) .description(p.getDisplayDescription()) diff --git a/app/src/main/java/io/xpipe/app/ext/ActionProvider.java b/app/src/main/java/io/xpipe/app/ext/ActionProvider.java index 28009cf91..16f9d109f 100644 --- a/app/src/main/java/io/xpipe/app/ext/ActionProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ActionProvider.java @@ -26,6 +26,107 @@ public interface ActionProvider { } } + default void init() {} + + default String getId() { + return null; + } + + default boolean isActive() { + return true; + } + + default String getProFeatureId() { + return null; + } + + default LauncherCallSite getLauncherCallSite() { + return null; + } + + default DataStoreCallSite getDataStoreCallSite() { + return null; + } + + default DefaultDataStoreCallSite getDefaultDataStoreCallSite() { + return null; + } + + interface Action { + + boolean requiresJavaFXPlatform(); + + void execute() throws Exception; + } + + interface LauncherCallSite { + + String getId(); + + Action createAction(URI uri) throws Exception; + } + + interface XPipeLauncherCallSite extends LauncherCallSite { + + String getId(); + + default Action createAction(URI uri) throws Exception { + var args = new ArrayList<>(Arrays.asList(uri.getPath().substring(1).split("/"))); + args.addFirst(uri.getHost()); + return createAction(args); + } + + Action createAction(List args) throws Exception; + } + + interface DefaultDataStoreCallSite { + + Action createAction(DataStoreEntryRef store); + + Class getApplicableClass(); + + default boolean isApplicable(DataStoreEntryRef o) { + return true; + } + } + + interface DataStoreCallSite { + + default boolean isSystemAction() { + return false; + } + + default boolean canLinkTo() { + return false; + } + + Action createAction(DataStoreEntryRef store); + + Class getApplicableClass(); + + default boolean isMajor(DataStoreEntryRef o) { + return false; + } + + default boolean isApplicable(DataStoreEntryRef o) { + return true; + } + + ObservableValue getName(DataStoreEntryRef store); + + String getIcon(DataStoreEntryRef store); + + default ActiveType activeType() { + return ActiveType.ONLY_SHOW_IF_ENABLED; + } + + enum ActiveType { + ONLY_SHOW_IF_ENABLED, + ALWAYS_SHOW, + ALWAYS_ENABLE + } + } + class Loader implements ModuleLayerLoader { @Override @@ -53,106 +154,4 @@ public interface ActionProvider { return false; } } - - interface Action { - - boolean requiresJavaFXPlatform(); - - void execute() throws Exception; - } - - default void init() { - } - - default String getId() { - return null; - } - - default boolean isActive() { - return true; - } - - default String getProFeatureId() { - return null; - } - - interface LauncherCallSite { - - String getId(); - - Action createAction(URI uri) throws Exception; - } - - interface XPipeLauncherCallSite extends LauncherCallSite { - - String getId(); - - default Action createAction(URI uri) throws Exception { - var args = new ArrayList<>(Arrays.asList(uri.getPath().substring(1).split("/"))); - args.addFirst(uri.getHost()); - return createAction(args); - } - - Action createAction(List args) throws Exception; - } - - default LauncherCallSite getLauncherCallSite() { - return null; - } - - default DataStoreCallSite getDataStoreCallSite() { - return null; - } - - default DefaultDataStoreCallSite getDefaultDataStoreCallSite() { - return null; - } - - interface DefaultDataStoreCallSite { - - Action createAction(DataStoreEntryRef store); - - Class getApplicableClass(); - - default boolean isApplicable(DataStoreEntryRef o) { - return true; - } - } - - interface DataStoreCallSite { - - enum ActiveType { - ONLY_SHOW_IF_ENABLED, - ALWAYS_SHOW, - ALWAYS_ENABLE - } - - default boolean isSystemAction() { - return false; - } - - default boolean canLinkTo() { - return false; - } - - Action createAction(DataStoreEntryRef store); - - Class getApplicableClass(); - - default boolean isMajor(DataStoreEntryRef o) { - return false; - } - - default boolean isApplicable(DataStoreEntryRef o) { - return true; - } - - ObservableValue getName(DataStoreEntryRef store); - - String getIcon(DataStoreEntryRef store); - - default ActiveType activeType() { - return ActiveType.ONLY_SHOW_IF_ENABLED; - } - } } diff --git a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java index 4216ce118..c2e95c564 100644 --- a/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/DataStoreProvider.java @@ -123,8 +123,7 @@ public interface DataStoreProvider { return true; } - default void postInit(){ - } + default void postInit() {} default void storageInit() {} diff --git a/app/src/main/java/io/xpipe/app/ext/DataStoreProviders.java b/app/src/main/java/io/xpipe/app/ext/DataStoreProviders.java index b8f8d25e0..7931ae3ef 100644 --- a/app/src/main/java/io/xpipe/app/ext/DataStoreProviders.java +++ b/app/src/main/java/io/xpipe/app/ext/DataStoreProviders.java @@ -62,7 +62,9 @@ public class DataStoreProviders { String.join("_", split), String.join("-", split), split.stream() - .map(s -> s.equals(split.getFirst()) ? s : s.substring(0, 1).toUpperCase() + s.substring(1)) + .map(s -> s.equals(split.getFirst()) + ? s + : s.substring(0, 1).toUpperCase() + s.substring(1)) .collect(Collectors.joining())); } diff --git a/app/src/main/java/io/xpipe/app/ext/PrefsProvider.java b/app/src/main/java/io/xpipe/app/ext/PrefsProvider.java index c1f0b954a..14039c655 100644 --- a/app/src/main/java/io/xpipe/app/ext/PrefsProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/PrefsProvider.java @@ -10,6 +10,22 @@ public abstract class PrefsProvider { private static List ALL; + public static List getAll() { + return ALL; + } + + @SuppressWarnings("unchecked") + public static T get(Class c) { + return (T) ALL.stream() + .filter(prefsProvider -> prefsProvider.getClass().equals(c)) + .findAny() + .orElseThrow(); + } + + public abstract void addPrefs(PrefsHandler handler); + + public abstract void initDefaultValues(); + public static class Loader implements ModuleLayerLoader { @Override @@ -29,20 +45,4 @@ public abstract class PrefsProvider { return false; } } - - public static List getAll() { - return ALL; - } - - @SuppressWarnings("unchecked") - public static T get(Class c) { - return (T) ALL.stream() - .filter(prefsProvider -> prefsProvider.getClass().equals(c)) - .findAny() - .orElseThrow(); - } - - public abstract void addPrefs(PrefsHandler handler); - - public abstract void initDefaultValues(); } diff --git a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java index 3d6999504..565e379e0 100644 --- a/app/src/main/java/io/xpipe/app/ext/ScanProvider.java +++ b/app/src/main/java/io/xpipe/app/ext/ScanProvider.java @@ -14,6 +14,20 @@ import java.util.stream.Collectors; public abstract class ScanProvider { + private static List ALL; + + public static List getAll() { + return ALL; + } + + public ScanOperation create(DataStore store) { + return null; + } + + public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception { + return null; + } + @Value public static class ScanOperation { String nameKey; @@ -22,8 +36,6 @@ public abstract class ScanProvider { FailableRunnable scanner; } - private static List ALL; - public static class Loader implements ModuleLayerLoader { @Override @@ -45,16 +57,4 @@ public abstract class ScanProvider { return false; } } - - public static List getAll() { - return ALL; - } - - public ScanOperation create(DataStore store) { - return null; - } - - public ScanOperation create(DataStoreEntry entry, ShellControl sc) throws Exception { - return null; - } } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java b/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java index 5744e27c0..f0c48ba66 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java @@ -10,11 +10,10 @@ import java.util.function.Supplier; public class ContextMenuAugment> implements Augment { + private static ContextMenu currentContextMenu; private final Predicate show; private final Supplier contextMenu; - private static ContextMenu currentContextMenu; - public ContextMenuAugment(Predicate show, Supplier contextMenu) { this.show = show; this.contextMenu = contextMenu; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/DraggableAugment.java b/app/src/main/java/io/xpipe/app/fxcomps/augment/DraggableAugment.java index 19d587ee3..170b1b22a 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/augment/DraggableAugment.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/augment/DraggableAugment.java @@ -31,7 +31,6 @@ public class DraggableAugment> implements Augment circle.setTranslateY(initialTranslateY + deltaY); lastMouseX = mouseEvent.getSceneX(); lastMouseY = mouseEvent.getSceneY(); - }); circle.setOnMouseEntered(mouseEvent -> { if (!mouseEvent.isPrimaryButtonDown()) { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoiceComp.java index 3ce1cda07..7cc3a2735 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoiceComp.java @@ -25,15 +25,9 @@ import java.util.stream.Collectors; @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) public class ChoiceComp extends Comp>> { - public static ChoiceComp ofTranslatable(Property value, List range, boolean includeNone) { - var map = range.stream().collect(Collectors.toMap(o -> o, Translatable::toTranslatedString, (v1, v2) -> v2, LinkedHashMap::new)); - return new ChoiceComp<>(value, map,includeNone); - } - Property value; ObservableValue>> range; boolean includeNone; - public ChoiceComp(Property value, Map> range, boolean includeNone) { this.value = value; this.range = new SimpleObjectProperty<>(range); @@ -46,6 +40,14 @@ public class ChoiceComp extends Comp>> { this.includeNone = includeNone; } + public static ChoiceComp ofTranslatable( + Property value, List range, boolean includeNone) { + var map = range.stream() + .collect( + Collectors.toMap(o -> o, Translatable::toTranslatedString, (v1, v2) -> v2, LinkedHashMap::new)); + return new ChoiceComp<>(value, map, includeNone); + } + @Override public CompStructure> createBase() { var cb = new ComboBox(); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoicePaneComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoicePaneComp.java index 89b6b8625..2bc5f1d51 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoicePaneComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ChoicePaneComp.java @@ -30,7 +30,7 @@ public class ChoicePaneComp extends Comp> { @Override public CompStructure createBase() { - var list = FXCollections.observableArrayList(entries); + var list = FXCollections.observableArrayList(entries); var cb = new ComboBox<>(list); cb.setOnKeyPressed(event -> { if (!cb.isShowing() && event.getCode().equals(KeyCode.ENTER)) { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java index 42d86ad33..e4ac6b2c2 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ContextualFileReferenceChoiceComp.java @@ -37,8 +37,7 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp { private final Property filePath; public ContextualFileReferenceChoiceComp( - ObservableValue> fileSystem, Property filePath - ) { + ObservableValue> fileSystem, Property filePath) { this.fileSystem = new SimpleObjectProperty<>(); SimpleChangeListener.apply(fileSystem, val -> { this.fileSystem.setValue(val); @@ -48,33 +47,39 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp { @Override protected Region createSimple() { - var fileNameComp = new TextFieldComp(filePath).apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)).styleClass(Styles.LEFT_PILL).grow( - false, true); + var fileNameComp = new TextFieldComp(filePath) + .apply(struc -> HBox.setHgrow(struc.get(), Priority.ALWAYS)) + .styleClass(Styles.LEFT_PILL) + .grow(false, true); var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> { - StandaloneFileBrowser.openSingleFile(() -> fileSystem.getValue(), fileStore -> { - if (fileStore == null) { - filePath.setValue(null); - fileSystem.setValue(null); - } else { - filePath.setValue(fileStore.getPath()); - fileSystem.setValue(fileStore.getFileSystem()); - } - }); - }).styleClass(Styles.CENTER_PILL).grow(false, true); + StandaloneFileBrowser.openSingleFile(() -> fileSystem.getValue(), fileStore -> { + if (fileStore == null) { + filePath.setValue(null); + fileSystem.setValue(null); + } else { + filePath.setValue(fileStore.getPath()); + fileSystem.setValue(fileStore.getFileSystem()); + } + }); + }) + .styleClass(Styles.CENTER_PILL) + .grow(false, true); + var canGitShare = BindingsHelper.persist(Bindings.createBooleanBinding( + () -> { + if (!AppPrefs.get().enableGitStorage().get() + || filePath.getValue() == null + || ContextualFileReference.of(filePath.getValue()).isInDataDirectory()) { + return false; + } - var canGitShare = BindingsHelper.persist(Bindings.createBooleanBinding(() -> { - if (!AppPrefs.get().enableGitStorage().get() || filePath.getValue() == null || ContextualFileReference.of(filePath.getValue()) - .isInDataDirectory()) { - return false; - } - - return true; - }, filePath, AppPrefs.get().enableGitStorage())); + return true; + }, + filePath, + AppPrefs.get().enableGitStorage())); var gitShareButton = new ButtonComp(null, new FontIcon("mdi2g-git"), () -> { - if (filePath.getValue() == null || filePath.getValue().isBlank() || - !canGitShare.get()) { + if (filePath.getValue() == null || filePath.getValue().isBlank() || !canGitShare.get()) { return; } @@ -84,10 +89,12 @@ public class ContextualFileReferenceChoiceComp extends SimpleComp { var source = Path.of(filePath.getValue()); if (Files.exists(source)) { var shouldCopy = AppWindowHelper.showBlockingAlert(alert -> { - alert.setTitle(AppI18n.get("confirmGitShareTitle")); - alert.setHeaderText(AppI18n.get("confirmGitShareHeader")); - alert.setAlertType(Alert.AlertType.CONFIRMATION); - }).map(buttonType -> buttonType.getButtonData().isDefaultButton()).orElse(false); + alert.setTitle(AppI18n.get("confirmGitShareTitle")); + alert.setHeaderText(AppI18n.get("confirmGitShareHeader")); + alert.setAlertType(Alert.AlertType.CONFIRMATION); + }) + .map(buttonType -> buttonType.getButtonData().isDefaultButton()) + .orElse(false); if (!shouldCopy) { return; } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java index 6debd15c1..76a8c6485 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreChoiceComp.java @@ -34,41 +34,41 @@ import java.util.function.Predicate; @RequiredArgsConstructor public class DataStoreChoiceComp extends SimpleComp { - public static DataStoreChoiceComp other( - Property> selected, Class clazz, Predicate> filter, StoreCategoryWrapper initialCategory) { - return new DataStoreChoiceComp<>(Mode.OTHER, null, selected, clazz, filter, initialCategory); - } - - public static DataStoreChoiceComp proxy(Property> selected, StoreCategoryWrapper initialCategory) { - return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, null, initialCategory); - } - - public static DataStoreChoiceComp host(Property> selected, StoreCategoryWrapper initialCategory) { - return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, null, initialCategory); - } - - public enum Mode { - HOST, - OTHER, - PROXY - } - private final Mode mode; private final DataStoreEntry self; private final Property> selected; private final Class storeClass; private final Predicate> applicableCheck; private final StoreCategoryWrapper initialCategory; - private Popover popover; + public static DataStoreChoiceComp other( + Property> selected, + Class clazz, + Predicate> filter, + StoreCategoryWrapper initialCategory) { + return new DataStoreChoiceComp<>(Mode.OTHER, null, selected, clazz, filter, initialCategory); + } + + public static DataStoreChoiceComp proxy( + Property> selected, StoreCategoryWrapper initialCategory) { + return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, null, initialCategory); + } + + public static DataStoreChoiceComp host( + Property> selected, StoreCategoryWrapper initialCategory) { + return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, null, initialCategory); + } + private Popover getPopover() { // Rebuild popover if we have a non-null condition to allow for the content to be updated in case the condition // changed if (popover == null || applicableCheck != null) { var cur = StoreViewState.get().getActiveCategory().getValue(); var selectedCategory = new SimpleObjectProperty<>( - initialCategory != null ? (initialCategory.getRoot().equals(cur.getRoot()) ? cur : initialCategory) : cur); + initialCategory != null + ? (initialCategory.getRoot().equals(cur.getRoot()) ? cur : initialCategory) + : cur); var filterText = new SimpleStringProperty(); popover = new Popover(); Predicate applicable = storeEntryWrapper -> { @@ -85,8 +85,7 @@ public class DataStoreChoiceComp extends SimpleComp { return storeClass.isAssignableFrom(e.getStore().getClass()) && e.getValidity().isUsable() - && (applicableCheck == null - || applicableCheck.test(e.ref())); + && (applicableCheck == null || applicableCheck.test(e.ref())); }; var section = StoreSectionMiniComp.createList( StoreSection.createTopLevel( @@ -102,11 +101,13 @@ public class DataStoreChoiceComp extends SimpleComp { comp.disable(new SimpleBooleanProperty(true)); } }); - var category = new DataStoreCategoryChoiceComp(initialCategory != null ? initialCategory.getRoot() : null, StoreViewState.get().getActiveCategory(), - selectedCategory).styleClass(Styles.LEFT_PILL); - var filter = new FilterComp(filterText) - .styleClass(Styles.CENTER_PILL) - .hgrow(); + var category = new DataStoreCategoryChoiceComp( + initialCategory != null ? initialCategory.getRoot() : null, + StoreViewState.get().getActiveCategory(), + selectedCategory) + .styleClass(Styles.LEFT_PILL); + var filter = + new FilterComp(filterText).styleClass(Styles.CENTER_PILL).hgrow(); var addButton = Comp.of(() -> { MenuButton m = new MenuButton(null, new FontIcon("mdi2p-plus-box-outline")); @@ -124,13 +125,21 @@ public class DataStoreChoiceComp extends SimpleComp { .apply(struc -> { // Ugly solution to focus the text field // Somehow this does not work through the normal on shown listeners - struc.get().getChildren().get(0).focusedProperty().addListener((observable, oldValue, newValue) -> { - if (newValue) { - ((StackPane) struc.get().getChildren().get(1)).getChildren().get(1).requestFocus(); - } - }); + struc.get() + .getChildren() + .get(0) + .focusedProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue) { + ((StackPane) struc.get().getChildren().get(1)) + .getChildren() + .get(1) + .requestFocus(); + } + }); }) - .createStructure().get(); + .createStructure() + .get(); var r = section.vgrow().createRegion(); var content = new VBox(top, r); content.setFillWidth(true); @@ -167,7 +176,9 @@ public class DataStoreChoiceComp extends SimpleComp { var button = new ButtonComp( Bindings.createStringBinding( () -> { - return selected.getValue() != null ? toName(selected.getValue().getEntry()) : null; + return selected.getValue() != null + ? toName(selected.getValue().getEntry()) + : null; }, selected), () -> {}); @@ -213,4 +224,10 @@ public class DataStoreChoiceComp extends SimpleComp { r.maxWidthProperty().bind(pane.widthProperty()); return pane; } + + public enum Mode { + HOST, + OTHER, + PROXY + } } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreListChoiceComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreListChoiceComp.java index 946165b61..dffe85980 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreListChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/DataStoreListChoiceComp.java @@ -22,9 +22,11 @@ public class DataStoreListChoiceComp extends SimpleComp { private final Predicate> applicableCheck; private final StoreCategoryWrapper initialCategory; - public DataStoreListChoiceComp(ListProperty> selectedList, Class storeClass, Predicate> applicableCheck, - StoreCategoryWrapper initialCategory - ) { + public DataStoreListChoiceComp( + ListProperty> selectedList, + Class storeClass, + Predicate> applicableCheck, + StoreCategoryWrapper initialCategory) { this.selectedList = selectedList; this.storeClass = storeClass; this.applicableCheck = applicableCheck; @@ -34,30 +36,35 @@ public class DataStoreListChoiceComp extends SimpleComp { @Override protected Region createSimple() { var list = new ListBoxViewComp<>(selectedList, selectedList, t -> { - if (t == null) { - return null; - } + if (t == null) { + return null; + } - var label = new LabelComp(t.get().getName()).apply(struc -> struc.get().setGraphic(PrettyImageHelper.ofFixedSmallSquare( - t.get().getProvider().getDisplayIconFileName(t.getStore())).createRegion())); - var delete = new IconButtonComp("mdal-delete_outline", () -> { - selectedList.remove(t); - }); - return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry"); - }).padding(new Insets(0)).apply(struc -> struc.get().setMinHeight(0)).apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5)); + var label = new LabelComp(t.get().getName()).apply(struc -> struc.get() + .setGraphic(PrettyImageHelper.ofFixedSmallSquare( + t.get().getProvider().getDisplayIconFileName(t.getStore())) + .createRegion())); + var delete = new IconButtonComp("mdal-delete_outline", () -> { + selectedList.remove(t); + }); + return new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry"); + }) + .padding(new Insets(0)) + .apply(struc -> struc.get().setMinHeight(0)) + .apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5)); var selected = new SimpleObjectProperty>(); - var add = new DataStoreChoiceComp<>(DataStoreChoiceComp.Mode.OTHER, null, selected, storeClass, applicableCheck, initialCategory); + var add = new DataStoreChoiceComp<>( + DataStoreChoiceComp.Mode.OTHER, null, selected, storeClass, applicableCheck, initialCategory); selected.addListener((observable, oldValue, newValue) -> { if (newValue != null) { - if (!selectedList.contains(newValue) - && (applicableCheck == null - || applicableCheck.test(newValue))) { + if (!selectedList.contains(newValue) && (applicableCheck == null || applicableCheck.test(newValue))) { selectedList.add(newValue); } selected.setValue(null); } }); - var vbox = new VerticalComp(List.of(list, Comp.vspacer(5), add)).apply(struc -> struc.get().setFillWidth(true)); + var vbox = new VerticalComp(List.of(list, Comp.vspacer(5), add)) + .apply(struc -> struc.get().setFillWidth(true)); return vbox.styleClass("data-store-list-choice-comp").createRegion(); } } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/IntFieldComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/IntFieldComp.java index 8615c7bad..0beffef90 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/IntFieldComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/IntFieldComp.java @@ -67,7 +67,10 @@ public class IntFieldComp extends Comp> { }); text.textProperty().addListener((observableValue, oldValue, newValue) -> { - if (newValue == null || newValue.isEmpty() || (minValue < 0 && "-".equals(newValue)) || !newValue.matches("-?\\d+")) { + if (newValue == null + || newValue.isEmpty() + || (minValue < 0 && "-".equals(newValue)) + || !newValue.matches("-?\\d+")) { value.setValue(null); return; } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageComp.java index abf30fc85..74ad4ddee 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageComp.java @@ -79,7 +79,9 @@ public class PrettyImageComp extends SimpleComp { } else if (AppImages.hasNormalImage(image.getValue().replace("-dark", ""))) { return AppImages.image(image.getValue().replace("-dark", "")); } else { - TrackEvent.withWarn("Image file not found").tag("file",image.getValue()).handle(); + TrackEvent.withWarn("Image file not found") + .tag("file", image.getValue()) + .handle(); return null; } }, @@ -89,9 +91,11 @@ public class PrettyImageComp extends SimpleComp { storeIcon.setSmooth(true); stack.getChildren().add(storeIcon); - Consumer update = val -> { - var fixed = val != null ? FileNames.getBaseName(val) + (AppPrefs.get().theme.get().isDark() ? "-dark" : "") + "." + FileNames.getExtension(val) : null; + var fixed = val != null + ? FileNames.getBaseName(val) + (AppPrefs.get().theme.get().isDark() ? "-dark" : "") + "." + + FileNames.getExtension(val) + : null; image.set(fixed); if (val == null) { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageHelper.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageHelper.java index 7a7e6d448..1aaf75cbc 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageHelper.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettyImageHelper.java @@ -35,20 +35,21 @@ public class PrettyImageHelper { if (rasterized.isPresent()) { return rasterized.get(); } else { - return img.endsWith(".svg") ? new PrettySvgComp(new SimpleStringProperty(img), w, h) : new PrettyImageComp(new SimpleStringProperty(img), w, h); + return img.endsWith(".svg") + ? new PrettySvgComp(new SimpleStringProperty(img), w, h) + : new PrettyImageComp(new SimpleStringProperty(img), w, h); } } - public static Comp ofSvg(ObservableValue img, int w, int h) { + public static Comp ofSvg(ObservableValue img, int w, int h) { return new PrettySvgComp(img, w, h); } - public static Comp ofRasterized(ObservableValue img, int w, int h) { + public static Comp ofRasterized(ObservableValue img, int w, int h) { return new PrettyImageComp(img, w, h); } - public static Comp ofFixedSmallSquare(String img) { + public static Comp ofFixedSmallSquare(String img) { return ofFixedSize(img, 16, 16); } - } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettySvgComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettySvgComp.java index bb1a1939a..2519304c8 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettySvgComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/PrettySvgComp.java @@ -85,7 +85,10 @@ public class PrettySvgComp extends SimpleComp { stack.getChildren().add(node); Consumer update = val -> { - var fixed = val != null ? FileNames.getBaseName(val) + (AppPrefs.get().theme.get().isDark() ? "-dark" : "") + "." + FileNames.getExtension(val) : null; + var fixed = val != null + ? FileNames.getBaseName(val) + (AppPrefs.get().theme.get().isDark() ? "-dark" : "") + "." + + FileNames.getExtension(val) + : null; image.set(fixed); }; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java index 61e0c56cc..387e2ef03 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/SecretFieldComp.java @@ -15,6 +15,12 @@ import java.util.Objects; public class SecretFieldComp extends Comp> { + private final Property value; + + public SecretFieldComp(Property value) { + this.value = value; + } + public static SecretFieldComp ofString(Property s) { var prop = new SimpleObjectProperty<>(s.getValue() != null ? InPlaceSecretValue.of(s.getValue()) : null); prop.addListener((observable, oldValue, newValue) -> { @@ -26,12 +32,6 @@ public class SecretFieldComp extends Comp> { return new SecretFieldComp(prop); } - private final Property value; - - public SecretFieldComp(Property value) { - this.value = value; - } - protected InPlaceSecretValue encrypt(char[] c) { return InPlaceSecretValue.of(c); } @@ -47,7 +47,8 @@ public class SecretFieldComp extends Comp> { value.addListener((c, o, n) -> { PlatformThread.runLaterIfNeeded(() -> { // Check if control value is the same. Then don't set it as that might cause bugs - if ((n == null && text.getText().isEmpty()) || Objects.equals(text.getText(), n != null ? n.getSecretValue() : null)) { + if ((n == null && text.getText().isEmpty()) + || Objects.equals(text.getText(), n != null ? n.getSecretValue() : null)) { return; } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryComp.java index b823d742e..d44e6e642 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryComp.java @@ -42,52 +42,75 @@ public class StoreCategoryComp extends SimpleComp { @Override protected Region createSimple() { - var i = Bindings.createStringBinding(() -> { - if (!DataStorage.get().supportsSharing() || !category.getCategory().canShare()) { - return "mdal-keyboard_arrow_right"; - } + var i = Bindings.createStringBinding( + () -> { + if (!DataStorage.get().supportsSharing() + || !category.getCategory().canShare()) { + return "mdal-keyboard_arrow_right"; + } - return category.getShare().getValue() ? "mdi2a-account-convert" : "mdi2a-account-cancel"; - }, category.getShare()); - var icon = new IconButtonComp(i).apply(struc -> AppFont.small(struc.get())).apply(struc -> { - struc.get().setAlignment(Pos.CENTER); - struc.get().setPadding(new Insets(0, 0, 6, 0)); - struc.get().setFocusTraversable(false); - }); - var name = new LazyTextFieldComp(category.nameProperty()).apply(struc -> { - struc.get().prefWidthProperty().unbind(); - struc.get().setPrefWidth(150); - struc.getTextField().minWidthProperty().bind(struc.get().widthProperty()); - }).styleClass("name").createRegion(); + return category.getShare().getValue() ? "mdi2a-account-convert" : "mdi2a-account-cancel"; + }, + category.getShare()); + var icon = new IconButtonComp(i) + .apply(struc -> AppFont.small(struc.get())) + .apply(struc -> { + struc.get().setAlignment(Pos.CENTER); + struc.get().setPadding(new Insets(0, 0, 6, 0)); + struc.get().setFocusTraversable(false); + }); + var name = new LazyTextFieldComp(category.nameProperty()) + .apply(struc -> { + struc.get().prefWidthProperty().unbind(); + struc.get().setPrefWidth(150); + struc.getTextField().minWidthProperty().bind(struc.get().widthProperty()); + }) + .styleClass("name") + .createRegion(); var showing = new SimpleBooleanProperty(); - var settings = new IconButtonComp("mdomz-settings").styleClass("settings").apply( - new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> { + var settings = new IconButtonComp("mdomz-settings") + .styleClass("settings") + .apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, () -> { var cm = createContextMenu(name); showing.bind(cm.showingProperty()); return cm; })); - var shownList = BindingsHelper.filteredContentBinding(category.getContainedEntries(), storeEntryWrapper -> { - return storeEntryWrapper.shouldShow(StoreViewState.get().getFilterString().getValue()); - }, StoreViewState.get().getFilterString()); + var shownList = BindingsHelper.filteredContentBinding( + category.getContainedEntries(), + storeEntryWrapper -> { + return storeEntryWrapper.shouldShow( + StoreViewState.get().getFilterString().getValue()); + }, + StoreViewState.get().getFilterString()); var count = new CountComp<>(shownList, category.getContainedEntries(), string -> "(" + string + ")"); var hover = new SimpleBooleanProperty(); var focus = new SimpleBooleanProperty(); - var h = new HorizontalComp( - List.of(icon, Comp.hspacer(4), Comp.of(() -> name), Comp.hspacer(), count.hide(BindingsHelper.persist(hover.or(showing).or(focus))), - settings.hide(BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not()))))); - h.apply(new ContextMenuAugment<>(mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name))); + var h = new HorizontalComp(List.of( + icon, + Comp.hspacer(4), + Comp.of(() -> name), + Comp.hspacer(), + count.hide(BindingsHelper.persist(hover.or(showing).or(focus))), + settings.hide( + BindingsHelper.persist(hover.not().and(showing.not()).and(focus.not()))))); + h.apply(new ContextMenuAugment<>( + mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name))); h.padding(new Insets(0, 10, 0, (category.getDepth() * 10))); - var categoryButton = new ButtonComp(null, h.createRegion(), category::select).styleClass("category-button").apply( - struc -> hover.bind(struc.get().hoverProperty())).apply(struc -> focus.bind(struc.get().focusedProperty())).accessibleText( - category.nameProperty()).grow(true, false); + var categoryButton = new ButtonComp(null, h.createRegion(), category::select) + .styleClass("category-button") + .apply(struc -> hover.bind(struc.get().hoverProperty())) + .apply(struc -> focus.bind(struc.get().focusedProperty())) + .accessibleText(category.nameProperty()) + .grow(true, false); - var l = category.getChildren().sorted(Comparator.comparing(storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT))); + var l = category.getChildren() + .sorted(Comparator.comparing( + storeCategoryWrapper -> storeCategoryWrapper.getName().toLowerCase(Locale.ROOT))); var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper)); var emptyBinding = Bindings.isEmpty(category.getChildren()); - var v = new VerticalComp( - List.of(categoryButton, children.hide(emptyBinding))); + var v = new VerticalComp(List.of(categoryButton, children.hide(emptyBinding))); v.styleClass("category"); v.apply(struc -> { SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> { @@ -104,26 +127,34 @@ public class StoreCategoryComp extends SimpleComp { var newCategory = new MenuItem(AppI18n.get("newCategory"), new FontIcon("mdi2p-plus-thick")); newCategory.setOnAction(event -> { - DataStorage.get().addStoreCategory(DataStoreCategory.createNew(category.getCategory().getUuid(), "New category")); + DataStorage.get() + .addStoreCategory( + DataStoreCategory.createNew(category.getCategory().getUuid(), "New category")); }); contextMenu.getItems().add(newCategory); if (DataStorage.get().supportsSharing() && category.getCategory().canShare()) { var share = new MenuItem(); - share.textProperty().bind(Bindings.createStringBinding(() -> { - if (category.getShare().getValue()) { - return AppI18n.get("unshare"); - } else { - return AppI18n.get("share"); - } - }, category.getShare())); - share.graphicProperty().bind(Bindings.createObjectBinding(() -> { - if (category.getShare().getValue()) { - return new FontIcon("mdi2b-block-helper"); - } else { - return new FontIcon("mdi2s-share"); - } - }, category.getShare())); + share.textProperty() + .bind(Bindings.createStringBinding( + () -> { + if (category.getShare().getValue()) { + return AppI18n.get("unshare"); + } else { + return AppI18n.get("share"); + } + }, + category.getShare())); + share.graphicProperty() + .bind(Bindings.createObjectBinding( + () -> { + if (category.getShare().getValue()) { + return new FontIcon("mdi2b-block-helper"); + } else { + return new FontIcon("mdi2s-share"); + } + }, + category.getShare())); share.setOnAction(event -> { category.getShare().setValue(!category.getShare().getValue()); }); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryListComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryListComp.java index 7d3caec7b..4339903b3 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryListComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/StoreCategoryListComp.java @@ -16,8 +16,7 @@ public class StoreCategoryListComp extends SimpleComp { @Override protected Region createSimple() { - return new VerticalComp(List.of( - new StoreCategoryComp(root))) + return new VerticalComp(List.of(new StoreCategoryComp(root))) .apply(struc -> struc.get().setFillWidth(true)) .apply(struc -> struc.get().setSpacing(3)) .styleClass("store-category-bar") diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java index bb284cd40..f5960527d 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/SvgView.java @@ -55,7 +55,9 @@ public class SvgView { private WebView createWebView() { var wv = new WebView(); - wv.getEngine().setUserDataDirectory(AppProperties.get().getDataDir().resolve("webview").toFile()); + wv.getEngine() + .setUserDataDirectory( + AppProperties.get().getDataDir().resolve("webview").toFile()); // Sometimes a web view might not render when the background is set to transparent, at least according to stack // overflow wv.setPageFill(Color.valueOf("#00000001")); @@ -67,7 +69,7 @@ public class SvgView { wv.setDisable(true); wv.getEngine().loadContent(svgContent.getValue() != null ? getHtml(svgContent.getValue()) : null); - SimpleChangeListener.apply(svgContent, n -> { + SimpleChangeListener.apply(svgContent, n -> { if (n == null) { wv.setOpacity(0.0); return; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/TextAreaComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/TextAreaComp.java index 40d3bf625..51952042c 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/TextAreaComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/TextAreaComp.java @@ -16,22 +16,9 @@ import java.util.Objects; public class TextAreaComp extends Comp { - @Value - @Builder - public static class Structure implements CompStructure { - AnchorPane pane; - TextArea textArea; - - @Override - public AnchorPane get() { - return pane; - } - } - private final Property currentValue; private final Property lastAppliedValue; private final boolean lazy; - public TextAreaComp(Property value) { this(value, false); } @@ -95,4 +82,16 @@ public class TextAreaComp extends Comp { return new Structure(anchorPane, text); } + + @Value + @Builder + public static class Structure implements CompStructure { + AnchorPane pane; + TextArea textArea; + + @Override + public AnchorPane get() { + return pane; + } + } } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/ToggleGroupComp.java b/app/src/main/java/io/xpipe/app/fxcomps/impl/ToggleGroupComp.java index d24626ca6..55cd6f56f 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/ToggleGroupComp.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/impl/ToggleGroupComp.java @@ -60,10 +60,7 @@ public class ToggleGroupComp extends Comp> { for (int i = 1; i < box.getChildren().size() - 1; i++) { box.getChildren().get(i).getStyleClass().add(Styles.CENTER_PILL); } - box.getChildren() - .getLast() - .getStyleClass() - .add(Styles.RIGHT_PILL); + box.getChildren().getLast().getStyleClass().add(Styles.RIGHT_PILL); } }); diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java index d5133514d..b557a61d4 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/BindingsHelper.java @@ -23,27 +23,10 @@ import java.util.function.Predicate; public class BindingsHelper { private static final Set REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public static void bindExclusive( - Property selected, Map> map, Property toBind) { - selected.addListener((c, o, n) -> { - toBind.unbind(); - toBind.bind(map.get(n)); - }); - - toBind.bind(map.get(selected.getValue())); - } - - @Value - private static class ReferenceEntry { - - WeakReference source; - Object target; - - public boolean canGc() { - return source.get() == null; - } - } + /* + TODO: Proper cleanup. Maybe with a separate thread? + */ + private static final Map, Set> BINDINGS = new ConcurrentHashMap<>(); static { ThreadHelper.createPlatformThread("referenceGC", true, () -> { @@ -62,15 +45,20 @@ public class BindingsHelper { .start(); } + public static void bindExclusive( + Property selected, Map> map, Property toBind) { + selected.addListener((c, o, n) -> { + toBind.unbind(); + toBind.bind(map.get(n)); + }); + + toBind.bind(map.get(selected.getValue())); + } + public static void linkPersistently(Object source, Object target) { REFERENCES.add(new ReferenceEntry(new WeakReference<>(source), target)); } - /* - TODO: Proper cleanup. Maybe with a separate thread? - */ - private static final Map, Set> BINDINGS = new ConcurrentHashMap<>(); - public static > T persist(T binding) { var dependencies = new HashSet(); while (dependencies.addAll(binding.getDependencies().stream() @@ -98,13 +86,17 @@ public class BindingsHelper { }); } - public static ObservableValue map(ObservableValue observableValue, Function mapper) { - return persist(Bindings.createObjectBinding(() -> { - return mapper.apply(observableValue.getValue()); - }, observableValue)); + public static ObservableValue map( + ObservableValue observableValue, Function mapper) { + return persist(Bindings.createObjectBinding( + () -> { + return mapper.apply(observableValue.getValue()); + }, + observableValue)); } - public static ObservableValue flatMap(ObservableValue observableValue, Function> mapper) { + public static ObservableValue flatMap( + ObservableValue observableValue, Function> mapper) { var prop = new SimpleObjectProperty(); Runnable runnable = () -> { prop.bind(mapper.apply(observableValue.getValue())); @@ -117,10 +109,12 @@ public class BindingsHelper { return prop; } - public static ObservableValue anyMatch(List> l) { - return BindingsHelper.persist(Bindings.createBooleanBinding(() -> { - return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue()); - }, l.toArray(ObservableValue[]::new))); + public static ObservableValue anyMatch(List> l) { + return BindingsHelper.persist(Bindings.createBooleanBinding( + () -> { + return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue()); + }, + l.toArray(ObservableValue[]::new))); } public static void bindMappedContent(ObservableList l1, ObservableList l2, Function map) { @@ -152,14 +146,17 @@ public class BindingsHelper { ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { cache.keySet().removeIf(t -> !l2.contains(t)); - setContent(l1, l2.stream() - .map(v -> { - if (!cache.containsKey(v)) { - cache.put(v, map.apply(v)); - } + setContent( + l1, + l2.stream() + .map(v -> { + if (!cache.containsKey(v)) { + cache.put(v, map.apply(v)); + } - return cache.get(v); - }).toList()); + return cache.get(v); + }) + .toList()); }; runnable.run(); l2.addListener((ListChangeListener) c -> { @@ -169,20 +166,24 @@ public class BindingsHelper { return l1; } - public static ObservableList cachedMappedContentBinding(ObservableList all, ObservableList shown, Function map) { + public static ObservableList cachedMappedContentBinding( + ObservableList all, ObservableList shown, Function map) { var cache = new HashMap(); ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { cache.keySet().removeIf(t -> !all.contains(t)); - setContent(l1, shown.stream() - .map(v -> { - if (!cache.containsKey(v)) { - cache.put(v, map.apply(v)); - } + setContent( + l1, + shown.stream() + .map(v -> { + if (!cache.containsKey(v)) { + cache.put(v, map.apply(v)); + } - return cache.get(v); - }).toList()); + return cache.get(v); + }) + .toList()); }; runnable.run(); shown.addListener((ListChangeListener) c -> { @@ -193,32 +194,40 @@ public class BindingsHelper { return l1; } - public static ObservableValue mappedBinding(ObservableValue observableValue, Function> mapper) { + public static ObservableValue mappedBinding( + ObservableValue observableValue, Function> mapper) { var binding = (Binding) observableValue.flatMap(mapper); return persist(binding); } -// public static ObservableValue mappedBinding(ObservableValue observableValue, Function> mapper) { -// var v = new SimpleObjectProperty(); -// SimpleChangeListener.apply(observableValue, val -> { -// v.unbind(); -// v.bind(mapper.apply(val)); -// }); -// return v; -// } - - public static ObservableList orderedContentBinding(ObservableList l2, Comparator comp, Observable... observables) { - return orderedContentBinding(l2, Bindings.createObjectBinding(() -> { - return new Comparator<>() { - @Override - public int compare(V o1, V o2) { - return comp.compare(o1, o2); - } - }; - }, observables)); + public static ObservableList orderedContentBinding( + ObservableList l2, Comparator comp, Observable... observables) { + return orderedContentBinding( + l2, + Bindings.createObjectBinding( + () -> { + return new Comparator<>() { + @Override + public int compare(V o1, V o2) { + return comp.compare(o1, o2); + } + }; + }, + observables)); } - public static ObservableList orderedContentBinding(ObservableList l2, ObservableValue> comp) { + // public static ObservableValue mappedBinding(ObservableValue observableValue, Function> mapper) { + // var v = new SimpleObjectProperty(); + // SimpleChangeListener.apply(observableValue, val -> { + // v.unbind(); + // v.bind(mapper.apply(val)); + // }); + // return v; + // } + + public static ObservableList orderedContentBinding( + ObservableList l2, ObservableValue> comp) { ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { setContent(l1, l2.stream().sorted(comp.getValue()).toList()); @@ -238,7 +247,8 @@ public class BindingsHelper { return filteredContentBinding(l2, new SimpleObjectProperty<>(predicate)); } - public static ObservableList filteredContentBinding(ObservableList l2, Predicate predicate, Observable... observables) { + public static ObservableList filteredContentBinding( + ObservableList l2, Predicate predicate, Observable... observables) { return filteredContentBinding( l2, Bindings.createObjectBinding( @@ -250,14 +260,18 @@ public class BindingsHelper { } }; }, - Arrays.stream(observables).filter( Objects::nonNull).toArray(Observable[]::new))); + Arrays.stream(observables).filter(Objects::nonNull).toArray(Observable[]::new))); } public static ObservableList filteredContentBinding( ObservableList l2, ObservableValue> predicate) { ObservableList l1 = FXCollections.observableList(new ArrayList<>()); Runnable runnable = () -> { - setContent(l1, predicate.getValue() != null ? l2.stream().filter(predicate.getValue()).toList() : l2); + setContent( + l1, + predicate.getValue() != null + ? l2.stream().filter(predicate.getValue()).toList() + : l2); }; runnable.run(); l2.addListener((ListChangeListener) c -> { @@ -313,4 +327,15 @@ public class BindingsHelper { // Other cases are more difficult target.setAll(newList); } + + @Value + private static class ReferenceEntry { + + WeakReference source; + Object target; + + public boolean canGc() { + return source.get() == null; + } + } } diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java b/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java index 1f00a3bf4..46c1924b8 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorEvent.java @@ -14,9 +14,8 @@ import java.util.concurrent.CopyOnWriteArraySet; @Getter public class ErrorEvent { - private String description; - private boolean terminal; - + private static final Map EVENT_BASES = new ConcurrentHashMap<>(); + private static final Set HANDLED = new CopyOnWriteArraySet<>(); @Builder.Default private final boolean omitted = false; @@ -24,25 +23,18 @@ public class ErrorEvent { private final boolean reportable = true; private final Throwable throwable; - + @Singular + private final List customActions; + private String description; + private boolean terminal; @Setter private boolean shouldSendDiagnostics; - @Singular private List attachments; - private String email; private String userReport; private boolean unhandled; - @Singular - private final List customActions; - - public void attachUserReport(String email, String text) { - this.email = email; - userReport = text; - } - public static ErrorEventBuilder fromThrowable(Throwable t) { if (EVENT_BASES.containsKey(t)) { return EVENT_BASES.remove(t).description(ExceptionConverter.convertMessage(t)); @@ -63,6 +55,41 @@ public class ErrorEvent { return builder().description(msg); } + public static T unreportableIfEndsWith(T t, String... s) { + return unreportableIf( + t, + t.getMessage() != null + && Arrays.stream(s).map(String::toLowerCase).anyMatch(string -> t.getMessage() + .toLowerCase(Locale.ROOT) + .endsWith(string))); + } + + public static T unreportableIfContains(T t, String... s) { + return unreportableIf( + t, + t.getMessage() != null + && Arrays.stream(s).map(String::toLowerCase).anyMatch(string -> t.getMessage() + .toLowerCase(Locale.ROOT) + .contains(string))); + } + + public static T unreportableIf(T t, boolean b) { + if (b) { + EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).expected()); + } + return t; + } + + public static T unreportable(T t) { + EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).expected()); + return t; + } + + public void attachUserReport(String email, String text) { + this.email = email; + userReport = text; + } + public List getThrowableChain() { var list = new ArrayList(); Throwable t = getThrowable(); @@ -74,8 +101,8 @@ public class ErrorEvent { } private boolean shouldIgnore(Throwable throwable) { - return (throwable != null && HANDLED.stream().anyMatch(t -> t == throwable) && !terminal) || - (throwable != null && throwable.getCause() != throwable && shouldIgnore(throwable.getCause())); + return (throwable != null && HANDLED.stream().anyMatch(t -> t == throwable) && !terminal) + || (throwable != null && throwable.getCause() != throwable && shouldIgnore(throwable.getCause())); } public void handle() { @@ -120,37 +147,4 @@ public class ErrorEvent { build().handle(); } } - - private static final Map EVENT_BASES = new ConcurrentHashMap<>(); - private static final Set HANDLED = new CopyOnWriteArraySet<>(); - - public static T unreportableIfEndsWith(T t, String... s) { - return unreportableIf( - t, - t.getMessage() != null - && Arrays.stream(s).map(String::toLowerCase).anyMatch(string -> t.getMessage() - .toLowerCase(Locale.ROOT) - .endsWith(string))); - } - - public static T unreportableIfContains(T t, String... s) { - return unreportableIf( - t, - t.getMessage() != null - && Arrays.stream(s).map(String::toLowerCase).anyMatch(string -> t.getMessage() - .toLowerCase(Locale.ROOT) - .contains(string))); - } - - public static T unreportableIf(T t, boolean b) { - if (b) { - EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).expected()); - } - return t; - } - - public static T unreportable(T t) { - EVENT_BASES.put(t, ErrorEvent.fromThrowable(t).expected()); - return t; - } } diff --git a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java index 91f31267a..e15e22094 100644 --- a/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java +++ b/app/src/main/java/io/xpipe/app/issue/ErrorHandlerComp.java @@ -114,9 +114,13 @@ public class ErrorHandlerComp extends SimpleComp { showing.set(true); Stage window; try { - window = AppWindowHelper.sideWindow(AppI18n.get("errorHandler"), w -> { - return setUpComp(event, w, finishLatch); - }, true, null); + window = AppWindowHelper.sideWindow( + AppI18n.get("errorHandler"), + w -> { + return setUpComp(event, w, finishLatch); + }, + true, + null); } catch (Throwable t) { showLatch.countDown(); finishLatch.countDown(); @@ -240,8 +244,7 @@ public class ErrorHandlerComp extends SimpleComp { actionBox.getChildren().add(ac); } - for (var action : - List.of(ErrorAction.ignore())) { + for (var action : List.of(ErrorAction.ignore())) { var ac = createActionComp(action); actionBox.getChildren().add(ac); } diff --git a/app/src/main/java/io/xpipe/app/issue/GuiErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/GuiErrorHandler.java index a72849e11..81096d6f9 100644 --- a/app/src/main/java/io/xpipe/app/issue/GuiErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/GuiErrorHandler.java @@ -30,8 +30,9 @@ public class GuiErrorHandler extends GuiErrorHandlerBase implements ErrorHandler } private void handleGui(ErrorEvent event) { - var lex = event.getThrowableChain().stream().flatMap(throwable -> throwable instanceof LicenseRequiredException le ? - Stream.of(le) : Stream.of()).findFirst(); + var lex = event.getThrowableChain().stream() + .flatMap(throwable -> throwable instanceof LicenseRequiredException le ? Stream.of(le) : Stream.of()) + .findFirst(); if (lex.isPresent()) { LicenseProvider.get().showLicenseAlert(lex.get()); event.setShouldSendDiagnostics(true); diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java index 491b3e8b1..878e45494 100644 --- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java +++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java @@ -24,59 +24,12 @@ import java.util.stream.Collectors; public class SentryErrorHandler implements ErrorHandler { private static final ErrorHandler INSTANCE = new SyncErrorHandler(new SentryErrorHandler()); + private boolean init; public static ErrorHandler getInstance() { return INSTANCE; } - private boolean init; - - public void handle(ErrorEvent ee) { - // Assume that this object is wrapped by a synchronous error handler - if (!init) { - AppProperties.init(); - AppState.init(); - if (AppProperties.get().getSentryUrl() != null) { - Sentry.init(options -> { - options.setDsn(AppProperties.get().getSentryUrl()); - options.setEnableUncaughtExceptionHandler(false); - options.setAttachServerName(false); - options.setRelease(AppProperties.get().getVersion()); - options.setEnableShutdownHook(false); - options.setProguardUuid(AppProperties.get().getBuildUuid().toString()); - options.setTag("os", System.getProperty("os.name")); - options.setTag("osVersion", System.getProperty("os.version")); - options.setTag("arch", System.getProperty("os.arch")); - options.setDist(XPipeDistributionType.get().getId()); - options.setTag("staging", String.valueOf(AppProperties.get().isStaging())); - options.setSendModules(false); - options.setAttachThreads(false); - options.setEnableDeduplication(false); - options.setCacheDirPath(AppProperties.get().getDataDir().resolve("cache").toString()); - }); - } - init = true; - } - - var id = captureEvent(ee); - if (id == null) { - return; - } - - var email = ee.getEmail(); - var hasEmail = email != null && !email.isBlank(); - var text = ee.getUserReport(); - if (hasUserReport(ee)) { - var fb = new UserFeedback(id); - if (hasEmail) { - fb.setEmail(email); - } - fb.setComments(text); - Sentry.captureUserFeedback(fb); - } - Sentry.flush(3000); - } - private static boolean hasUserReport(ErrorEvent ee) { var email = ee.getEmail(); var hasEmail = email != null && !email.isBlank(); @@ -181,7 +134,11 @@ public class SentryErrorHandler implements ErrorHandler { } s.setTag("hasLicense", String.valueOf(LicenseProvider.get().hasPaidLicense())); - s.setTag("updatesEnabled", AppPrefs.get() != null ? AppPrefs.get().automaticallyUpdate().getValue().toString() : "unknown"); + s.setTag( + "updatesEnabled", + AppPrefs.get() != null + ? AppPrefs.get().automaticallyUpdate().getValue().toString() + : "unknown"); s.setTag("initError", String.valueOf(OperationMode.isInStartup())); s.setTag( "developerMode", @@ -191,13 +148,16 @@ public class SentryErrorHandler implements ErrorHandler { s.setTag("terminal", Boolean.toString(ee.isTerminal())); s.setTag("omitted", Boolean.toString(ee.isOmitted())); s.setTag("diagnostics", Boolean.toString(ee.isShouldSendDiagnostics())); - s.setTag("logs", Boolean.toString(ee.isShouldSendDiagnostics() && !ee.getAttachments().isEmpty())); + s.setTag( + "logs", + Boolean.toString( + ee.isShouldSendDiagnostics() && !ee.getAttachments().isEmpty())); s.setTag("inShutdown", Boolean.toString(OperationMode.isInShutdown())); s.setTag("unhandled", Boolean.toString(ee.isUnhandled())); var exMessage = ee.getThrowable() != null ? ee.getThrowable().getMessage() : null; if (ee.getDescription() != null && !ee.getDescription().equals(exMessage) && ee.isShouldSendDiagnostics()) { - s.setTag("message", ee.getDescription().lines().collect(Collectors.joining(" "))); + s.setTag("message", ee.getDescription().lines().collect(Collectors.joining(" "))); } var user = new User(); @@ -208,4 +168,51 @@ public class SentryErrorHandler implements ErrorHandler { } s.setUser(user); } + + public void handle(ErrorEvent ee) { + // Assume that this object is wrapped by a synchronous error handler + if (!init) { + AppProperties.init(); + AppState.init(); + if (AppProperties.get().getSentryUrl() != null) { + Sentry.init(options -> { + options.setDsn(AppProperties.get().getSentryUrl()); + options.setEnableUncaughtExceptionHandler(false); + options.setAttachServerName(false); + options.setRelease(AppProperties.get().getVersion()); + options.setEnableShutdownHook(false); + options.setProguardUuid(AppProperties.get().getBuildUuid().toString()); + options.setTag("os", System.getProperty("os.name")); + options.setTag("osVersion", System.getProperty("os.version")); + options.setTag("arch", System.getProperty("os.arch")); + options.setDist(XPipeDistributionType.get().getId()); + options.setTag("staging", String.valueOf(AppProperties.get().isStaging())); + options.setSendModules(false); + options.setAttachThreads(false); + options.setEnableDeduplication(false); + options.setCacheDirPath( + AppProperties.get().getDataDir().resolve("cache").toString()); + }); + } + init = true; + } + + var id = captureEvent(ee); + if (id == null) { + return; + } + + var email = ee.getEmail(); + var hasEmail = email != null && !email.isBlank(); + var text = ee.getUserReport(); + if (hasUserReport(ee)) { + var fb = new UserFeedback(id); + if (hasEmail) { + fb.setEmail(email); + } + fb.setComments(text); + Sentry.captureUserFeedback(fb); + } + Sentry.flush(3000); + } } diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java b/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java index 9dce0cc67..323c5a9eb 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherCommand.java @@ -28,10 +28,11 @@ import java.util.concurrent.Callable; @CommandLine.Command( header = "Launches the XPipe daemon.", sortOptions = false, - showEndOfOptionsDelimiterInUsageHelp = true -) + showEndOfOptionsDelimiterInUsageHelp = true) public class LauncherCommand implements Callable { + @CommandLine.Parameters(paramLabel = "") + final List inputs = List.of(); @CommandLine.Option( names = {"--mode"}, description = "The mode to launch the daemon in or switch too", @@ -39,11 +40,11 @@ public class LauncherCommand implements Callable { converter = LauncherModeConverter.class) XPipeDaemonMode mode; - @CommandLine.Parameters(paramLabel = "") - final List inputs = List.of(); - public static void runLauncher(String[] args) { - TrackEvent.builder().type("debug").message("Launcher received commands: " + Arrays.asList(args)).handle(); + TrackEvent.builder() + .type("debug") + .message("Launcher received commands: " + Arrays.asList(args)) + .handle(); var cmd = new CommandLine(new LauncherCommand()); cmd.setExecutionExceptionHandler((ex, commandLine, parseResult) -> { @@ -81,14 +82,19 @@ public class LauncherCommand implements Callable { if (BeaconServer.isReachable()) { try (var con = new LauncherConnection()) { con.constructSocket(); - con.performSimpleExchange(FocusExchange.Request.builder().mode(getEffectiveMode()).build()); + con.performSimpleExchange(FocusExchange.Request.builder() + .mode(getEffectiveMode()) + .build()); if (!inputs.isEmpty()) { - con.performSimpleExchange(OpenExchange.Request.builder().arguments(inputs).build()); + con.performSimpleExchange( + OpenExchange.Request.builder().arguments(inputs).build()); } if (OsType.getLocal().equals(OsType.MACOS)) { Desktop.getDesktop().setOpenURIHandler(e -> { - con.performSimpleExchange(OpenExchange.Request.builder().arguments(List.of(e.getURI().toString())).build()); + con.performSimpleExchange(OpenExchange.Request.builder() + .arguments(List.of(e.getURI().toString())) + .build()); }); ThreadHelper.sleep(1000); } @@ -101,12 +107,17 @@ public class LauncherCommand implements Callable { // there might be another instance running, for example // starting up or listening on another port if (!AppDataLock.lock()) { - throw new IOException("Data directory " + AppProperties.get().getDataDir().toString() + " is already locked"); + throw new IOException( + "Data directory " + AppProperties.get().getDataDir().toString() + " is already locked"); } } catch (Exception ex) { var cli = XPipeInstallation.getLocalDefaultCliExecutable(); - ErrorEvent.fromThrowable(ex).term().description("Unable to connect to existing running daemon instance as it did not respond." + - " Either try to kill the process xpiped manually or use the command \"" + cli + "\" daemon stop --force.").handle(); + ErrorEvent.fromThrowable(ex) + .term() + .description("Unable to connect to existing running daemon instance as it did not respond." + + " Either try to kill the process xpiped manually or use the command \"" + cli + + "\" daemon stop --force.") + .handle(); } } @@ -122,7 +133,9 @@ public class LauncherCommand implements Callable { return XPipeDaemonMode.get(opModeName); } - return AppPrefs.get() != null ? AppPrefs.get().startupBehaviour().getValue().getMode() : XPipeDaemonMode.GUI; + return AppPrefs.get() != null + ? AppPrefs.get().startupBehaviour().getValue().getMode() + : XPipeDaemonMode.GUI; } @Override diff --git a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java index dae6444d6..7ac8f0bba 100644 --- a/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java +++ b/app/src/main/java/io/xpipe/app/launcher/LauncherInput.java @@ -24,9 +24,7 @@ public abstract class LauncherInput { return; } - TrackEvent.withDebug("Handling arguments") - .elements(arguments) - .handle(); + TrackEvent.withDebug("Handling arguments").elements(arguments).handle(); var all = new ArrayList(); arguments.forEach(s -> { @@ -104,6 +102,11 @@ public abstract class LauncherInput { Path file; + @Override + public boolean requiresJavaFXPlatform() { + return true; + } + @Override public void execute() { if (!Files.exists(file)) { @@ -116,12 +119,7 @@ public abstract class LauncherInput { var dir = Files.isDirectory(file) ? file : file.getParent(); AppLayoutModel.get().selectBrowser(); - BrowserModel.DEFAULT.openFileSystemAsync( DataStorage.get().local().ref(), model -> dir.toString(), null); - } - - @Override - public boolean requiresJavaFXPlatform() { - return true; + BrowserModel.DEFAULT.openFileSystemAsync(DataStorage.get().local().ref(), model -> dir.toString(), null); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/AboutCategory.java b/app/src/main/java/io/xpipe/app/prefs/AboutCategory.java index e9bb6843e..57e95b0e7 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AboutCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/AboutCategory.java @@ -63,9 +63,9 @@ public class AboutCategory extends AppPrefsCategory { null) .addComp( new TileButtonComp("eula", "eulaDescription", "mdi2c-card-text-outline", e -> { - Hyperlinks.open(Hyperlinks.EULA); - e.consume(); - }) + Hyperlinks.open(Hyperlinks.EULA); + e.consume(); + }) .grow(true, false), null) .buildComp(); @@ -86,31 +86,6 @@ public class AboutCategory extends AppPrefsCategory { return "about"; } - private Comp createProperties() { - var title = Comp.of(() -> { - return JfxHelper.createNamedEntry(AppI18n.get("xPipeClient"), "Version " + AppProperties.get().getVersion() + " (" - + AppProperties.get().getArch() + ")", "logo/logo_48x48.png"); - }).styleClass(Styles.TEXT_BOLD); - - var section = new OptionsBuilder() - .addComp(title, null) - .addComp(Comp.vspacer(10)) - .name("build") - .addComp( - new LabelComp(AppProperties.get().getBuild()), - null) - .name("runtimeVersion") - .addComp( - new LabelComp(System.getProperty("java.vm.version")), - null) - .name("virtualMachine") - .addComp( - new LabelComp(System.getProperty("java.vm.vendor") + " " + System.getProperty("java.vm.name")), - null) - .buildComp(); - return section.styleClass("properties-comp"); - } - @Override protected Comp create() { var props = createProperties().padding(new Insets(0, 0, 0, 15)); @@ -122,4 +97,29 @@ public class AboutCategory extends AppPrefsCategory { .styleClass("about-tab") .apply(struc -> struc.get().setPrefWidth(600)); } + + private Comp createProperties() { + var title = Comp.of(() -> { + return JfxHelper.createNamedEntry( + AppI18n.get("xPipeClient"), + "Version " + AppProperties.get().getVersion() + " (" + + AppProperties.get().getArch() + ")", + "logo/logo_48x48.png"); + }) + .styleClass(Styles.TEXT_BOLD); + + var section = new OptionsBuilder() + .addComp(title, null) + .addComp(Comp.vspacer(10)) + .name("build") + .addComp(new LabelComp(AppProperties.get().getBuild()), null) + .name("runtimeVersion") + .addComp(new LabelComp(System.getProperty("java.vm.version")), null) + .name("virtualMachine") + .addComp( + new LabelComp(System.getProperty("java.vm.vendor") + " " + System.getProperty("java.vm.name")), + null) + .buildComp(); + return section.styleClass("properties-comp"); + } } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index e0ca1cd6e..0976ff7ed 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -27,27 +27,172 @@ import java.util.*; public class AppPrefs { - @Value - public static class Mapping { + public static final Path DEFAULT_STORAGE_DIR = + AppProperties.get().getDataDir().resolve("storage"); + private static final String DEVELOPER_MODE_PROP = "io.xpipe.app.developerMode"; + private static AppPrefs INSTANCE; + private final List> mapping = new ArrayList<>(); + final BooleanProperty dontAutomaticallyStartVmSshServer = + map(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class); + final BooleanProperty dontAcceptNewHostKeys = + map(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class); + final BooleanProperty performanceMode = map(new SimpleBooleanProperty(false), "performanceMode", Boolean.class); + final BooleanProperty useBundledTools = map(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class); - String key; - Property property; - Class valueClass; - boolean vaultSpecific; + // Languages + // ========= + public final ObjectProperty theme = + map(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class); + final BooleanProperty useSystemFont = map(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class); + final Property uiScale = map(new SimpleObjectProperty<>(null), "uiScale", Integer.class); + final BooleanProperty saveWindowLocation = + map(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class); + // External terminal + // ================= + final ObjectProperty terminalType = + map(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class); + // Window opacity + // ============== + final DoubleProperty windowOpacity = map(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class); + // Custom terminal + // =============== + final StringProperty customTerminalCommand = + map(new SimpleStringProperty(""), "customTerminalCommand", String.class); + final BooleanProperty preferTerminalTabs = + map(new SimpleBooleanProperty(true), "preferTerminalTabs", Boolean.class); + final BooleanProperty clearTerminalOnInit = + map(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class); + public final BooleanProperty disableCertutilUse = + map(new SimpleBooleanProperty(false), "disableCertutilUse", Boolean.class); + public final BooleanProperty useLocalFallbackShell = + map(new SimpleBooleanProperty(false), "useLocalFallbackShell", Boolean.class); + public final BooleanProperty disableTerminalRemotePasswordPreparation = + map(new SimpleBooleanProperty(false), "disableTerminalRemotePasswordPreparation", Boolean.class); + public final Property alwaysConfirmElevation = + map(new SimpleObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class); + public final BooleanProperty dontCachePasswords = + map(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class); + public final BooleanProperty denyTempScriptCreation = + map(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class); + // Password manager + // ================ + final StringProperty passwordManagerCommand = + map(new SimpleStringProperty(""), "passwordManagerCommand", String.class); + // Start behaviour + // =============== + final ObjectProperty startupBehaviour = + map(new SimpleObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class); - public Mapping(String key, Property property, Class valueClass) { - this.key = key; - this.property = property; - this.valueClass = valueClass; - this.vaultSpecific = false; + // Lock + // ==== + // Git storage + // =========== + public final BooleanProperty enableGitStorage = + map(new SimpleBooleanProperty(false), "enableGitStorage", Boolean.class); + final StringProperty storageGitRemote = map(new SimpleStringProperty(""), "storageGitRemote", String.class); + // Close behaviour + // =============== + final ObjectProperty closeBehaviour = + map(new SimpleObjectProperty<>(CloseBehaviour.QUIT), "closeBehaviour", CloseBehaviour.class); + // External editor + // =============== + final ObjectProperty externalEditor = + map(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class); + final StringProperty customEditorCommand = map(new SimpleStringProperty(""), "customEditorCommand", String.class); + final BooleanProperty preferEditorTabs = map(new SimpleBooleanProperty(true), "preferEditorTabs", Boolean.class); + // Automatically update + // ==================== + final BooleanProperty automaticallyCheckForUpdates = + map(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class); + final BooleanProperty encryptAllVaultData = + mapVaultSpecific(new SimpleBooleanProperty(false), "encryptAllVaultData", Boolean.class); + final BooleanProperty enforceWindowModality = + map(new SimpleBooleanProperty(false), "enforceWindowModality", Boolean.class); + final BooleanProperty condenseConnectionDisplay = + map(new SimpleBooleanProperty(false), "condenseConnectionDisplay", Boolean.class); + // Storage + // ======= + final ObjectProperty storageDirectory = + map(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class); + private final AppPrefsStorageHandler vaultStorageHandler = + new AppPrefsStorageHandler(storageDirectory().getValue().resolve("preferences.json")); + final BooleanProperty developerMode = map(new SimpleBooleanProperty(false), "developerMode", Boolean.class); + final BooleanProperty developerDisableUpdateVersionCheck = + map(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class); + private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective = + bindDeveloperTrue(developerDisableUpdateVersionCheck); + final BooleanProperty developerDisableGuiRestrictions = + map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class); + private final ObservableBooleanValue developerDisableGuiRestrictionsEffective = + bindDeveloperTrue(developerDisableGuiRestrictions); + private final ObjectProperty language = + map(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), "language", SupportedLocale.class); + @Getter + private final Property lockPassword = new SimpleObjectProperty<>(); + @Getter + private final StringProperty lockCrypt = + mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class); + private final IntegerProperty editorReloadTimeout = + map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class); + private final BooleanProperty confirmDeletions = + map(new SimpleBooleanProperty(true), "confirmDeletions", Boolean.class); + @Getter + private final List categories; + private final AppPrefsStorageHandler globalStorageHandler = new AppPrefsStorageHandler( + AppProperties.get().getDataDir().resolve("settings").resolve("preferences.json")); + private final Map, Comp> customEntries = new LinkedHashMap<>(); + @Getter + private final Property selectedCategory; + private final PrefsHandler extensionHandler = new PrefsHandlerImpl(); + + private AppPrefs() { + this.categories = List.of( + new AboutCategory(), + new SystemCategory(), + new AppearanceCategory(), + new SyncCategory(), + new VaultCategory(), + new TerminalCategory(), + new EditorCategory(), + new LocalShellCategory(), + new SecurityCategory(), + new PasswordManagerCategory(), + new TroubleshootCategory(), + new DeveloperCategory()); + var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0); + if (selected == null) { + selected = 0; } + this.selectedCategory = new SimpleObjectProperty<>( + categories.get(selected >= 0 && selected < categories.size() ? selected : 0)); + } - public Mapping(String key, Property property, Class valueClass, boolean vaultSpecific) { - this.key = key; - this.property = property; - this.valueClass = valueClass; - this.vaultSpecific = vaultSpecific; - } + public static void init() { + INSTANCE = new AppPrefs(); + PrefsProvider.getAll().forEach(prov -> prov.addPrefs(INSTANCE.extensionHandler)); + INSTANCE.load(); + + INSTANCE.encryptAllVaultData.addListener((observableValue, aBoolean, t1) -> { + if (DataStorage.get() != null) { + DataStorage.get().forceRewrite(); + } + }); + } + + public static void setDefaults() { + INSTANCE.initDefaultValues(); + PrefsProvider.getAll().forEach(prov -> prov.initDefaultValues()); + } + + public static void reset() { + INSTANCE.save(); + + // Keep instance as we might need some values on shutdown, e.g. on update with terminals + // INSTANCE = null; + } + + public static AppPrefs get() { + return INSTANCE; } public boolean isDevelopmentEnvironment() { @@ -72,189 +217,82 @@ public class AppPrefs { developerMode()); } - private final List> mapping = new ArrayList<>(); - - public static final Path DEFAULT_STORAGE_DIR = - AppProperties.get().getDataDir().resolve("storage"); - private static final String DEVELOPER_MODE_PROP = "io.xpipe.app.developerMode"; - private static AppPrefs INSTANCE; - - // Languages - // ========= - - private final ObjectProperty language = - map(new SimpleObjectProperty<>(SupportedLocale.ENGLISH), "language", SupportedLocale.class); public ObservableValue language() { return language; } - final BooleanProperty dontAutomaticallyStartVmSshServer = map(new SimpleBooleanProperty(false), "dontAutomaticallyStartVmSshServer", Boolean.class); public ObservableBooleanValue dontAutomaticallyStartVmSshServer() { return dontAutomaticallyStartVmSshServer; } - final BooleanProperty dontAcceptNewHostKeys = map(new SimpleBooleanProperty(false), "dontAcceptNewHostKeys", Boolean.class); public ObservableBooleanValue dontAcceptNewHostKeys() { return dontAcceptNewHostKeys; } - final BooleanProperty performanceMode = map(new SimpleBooleanProperty(false), "performanceMode", Boolean.class); public ObservableBooleanValue performanceMode() { return performanceMode; } - final BooleanProperty useBundledTools = map(new SimpleBooleanProperty(false), "useBundledTools", Boolean.class); public ObservableBooleanValue useBundledTools() { return useBundledTools; } - public final ObjectProperty theme = map(new SimpleObjectProperty<>(), "theme", AppTheme.Theme.class); - final BooleanProperty useSystemFont = map(new SimpleBooleanProperty(true), "useSystemFont", Boolean.class); public ObservableValue useSystemFont() { return useSystemFont; } - final Property uiScale = map(new SimpleObjectProperty<>(null), "uiScale", Integer.class); public ReadOnlyProperty uiScale() { return uiScale; } - final BooleanProperty saveWindowLocation = map(new SimpleBooleanProperty(true), "saveWindowLocation", Boolean.class); - - // External terminal - // ================= - final ObjectProperty terminalType = - map(new SimpleObjectProperty<>(), "terminalType", ExternalTerminalType.class); - - // Lock - // ==== - - @Getter - private final Property lockPassword = new SimpleObjectProperty<>(); - @Getter - private final StringProperty lockCrypt = mapVaultSpecific(new SimpleStringProperty(), "workspaceLock", String.class); - - // Window opacity - // ============== - final DoubleProperty windowOpacity = map(new SimpleDoubleProperty(1.0), "windowOpacity", Double.class); - - // Custom terminal - // =============== - final StringProperty customTerminalCommand = map(new SimpleStringProperty(""), "customTerminalCommand", String.class); - - final BooleanProperty preferTerminalTabs = map(new SimpleBooleanProperty(true), "preferTerminalTabs", Boolean.class); - - final BooleanProperty clearTerminalOnInit = map(new SimpleBooleanProperty(true), "clearTerminalOnInit", Boolean.class); public ReadOnlyBooleanProperty clearTerminalOnInit() { return clearTerminalOnInit; } - public final BooleanProperty disableCertutilUse = map(new SimpleBooleanProperty(false), "disableCertutilUse", Boolean.class); public ObservableBooleanValue disableCertutilUse() { return disableCertutilUse; } - public final BooleanProperty useLocalFallbackShell = map(new SimpleBooleanProperty(false), "useLocalFallbackShell", Boolean.class); public ObservableBooleanValue useLocalFallbackShell() { return useLocalFallbackShell; } - public final BooleanProperty disableTerminalRemotePasswordPreparation = map(new SimpleBooleanProperty(false), "disableTerminalRemotePasswordPreparation", Boolean.class); public ObservableBooleanValue disableTerminalRemotePasswordPreparation() { return disableTerminalRemotePasswordPreparation; } - public final Property alwaysConfirmElevation = map(new SimpleObjectProperty<>(false), "alwaysConfirmElevation", Boolean.class); public ObservableValue alwaysConfirmElevation() { return alwaysConfirmElevation; } - public final BooleanProperty dontCachePasswords = map(new SimpleBooleanProperty(false), "dontCachePasswords", Boolean.class); public ObservableBooleanValue dontCachePasswords() { return dontCachePasswords; } - public final BooleanProperty denyTempScriptCreation = map(new SimpleBooleanProperty(false), "denyTempScriptCreation", Boolean.class); public ObservableBooleanValue denyTempScriptCreation() { return denyTempScriptCreation; } - // Password manager - // ================ - final StringProperty passwordManagerCommand = map(new SimpleStringProperty(""), "passwordManagerCommand", String.class); - - // Start behaviour - // =============== - final ObjectProperty startupBehaviour = - map(new SimpleObjectProperty<>(StartupBehaviour.GUI), "startupBehaviour", StartupBehaviour.class); - - - // Git storage - // =========== - public final BooleanProperty enableGitStorage = map(new SimpleBooleanProperty(false), "enableGitStorage", Boolean.class); public ObservableBooleanValue enableGitStorage() { return enableGitStorage; } - final StringProperty storageGitRemote = map(new SimpleStringProperty(""), "storageGitRemote", String.class); + public ObservableStringValue storageGitRemote() { return storageGitRemote; } - // Close behaviour - // =============== - final ObjectProperty closeBehaviour = - map(new SimpleObjectProperty<>(CloseBehaviour.QUIT), "closeBehaviour", CloseBehaviour.class); - - // External editor - // =============== - final ObjectProperty externalEditor = - map(new SimpleObjectProperty<>(), "externalEditor", ExternalEditorType.class); - - final StringProperty customEditorCommand = map(new SimpleStringProperty(""), "customEditorCommand", String.class); - private final IntegerProperty editorReloadTimeout = map(new SimpleIntegerProperty(1000), "editorReloadTimeout", Integer.class); - - final BooleanProperty preferEditorTabs = map(new SimpleBooleanProperty(true), "preferEditorTabs", Boolean.class); - - // Automatically update - // ==================== - final BooleanProperty automaticallyCheckForUpdates = map(new SimpleBooleanProperty(true), "automaticallyCheckForUpdates", Boolean.class); - private final BooleanProperty confirmDeletions = map(new SimpleBooleanProperty(true), "confirmDeletions", Boolean.class); - - - final BooleanProperty encryptAllVaultData = mapVaultSpecific(new SimpleBooleanProperty(false), "encryptAllVaultData", Boolean.class); public ObservableBooleanValue encryptAllVaultData() { return encryptAllVaultData; } - - final BooleanProperty enforceWindowModality = map(new SimpleBooleanProperty(false), "enforceWindowModality", Boolean.class); public ObservableBooleanValue enforceWindowModality() { return enforceWindowModality; } - - final BooleanProperty condenseConnectionDisplay = map(new SimpleBooleanProperty(false), "condenseConnectionDisplay", Boolean.class); public ObservableBooleanValue condenseConnectionDisplay() { return condenseConnectionDisplay; } - // Storage - // ======= - final ObjectProperty storageDirectory = - map(new SimpleObjectProperty<>(DEFAULT_STORAGE_DIR), "storageDirectory", Path.class); - - final BooleanProperty developerMode = map(new SimpleBooleanProperty(false), "developerMode", Boolean.class); - - final BooleanProperty developerDisableUpdateVersionCheck = - map(new SimpleBooleanProperty(false), "developerDisableUpdateVersionCheck", Boolean.class); - private final ObservableBooleanValue developerDisableUpdateVersionCheckEffective = - bindDeveloperTrue(developerDisableUpdateVersionCheck); - - final BooleanProperty developerDisableGuiRestrictions = - map(new SimpleBooleanProperty(false), "developerDisableGuiRestrictions", Boolean.class); - private final ObservableBooleanValue developerDisableGuiRestrictionsEffective = - bindDeveloperTrue(developerDisableGuiRestrictions); - public ReadOnlyProperty closeBehaviour() { return closeBehaviour; } @@ -286,7 +324,10 @@ public class AppPrefs { public boolean unlock(InPlaceSecretValue lockPw) { lockPassword.setValue(lockPw); - var check = PasswordLockSecretValue.builder().encryptedValue(lockCrypt.get()).build().getSecret(); + var check = PasswordLockSecretValue.builder() + .encryptedValue(lockCrypt.get()) + .build() + .getSecret(); if (!Arrays.equals(check, new char[] {'x', 'p', 'i', 'p', 'e'})) { lockPassword.setValue(null); return false; @@ -345,56 +386,6 @@ public class AppPrefs { return developerDisableGuiRestrictionsEffective; } - @Getter - private final List categories; - private final AppPrefsStorageHandler globalStorageHandler = new AppPrefsStorageHandler( - AppProperties.get().getDataDir().resolve("settings").resolve("preferences.json")); - private final AppPrefsStorageHandler vaultStorageHandler = new AppPrefsStorageHandler( - storageDirectory().getValue().resolve("preferences.json")); - private final Map, Comp> customEntries = new LinkedHashMap<>(); - @Getter - private final Property selectedCategory; - private final PrefsHandler extensionHandler = new PrefsHandlerImpl(); - - private AppPrefs() { - this.categories = List.of(new AboutCategory(), new SystemCategory(), new AppearanceCategory(), - new SyncCategory(), new VaultCategory(), new TerminalCategory(), new EditorCategory(), new LocalShellCategory(), new SecurityCategory(), - new PasswordManagerCategory(), new TroubleshootCategory(), new DeveloperCategory()); - var selected = AppCache.get("selectedPrefsCategory", Integer.class, () -> 0); - if (selected == null) { - selected = 0; - } - this.selectedCategory = new SimpleObjectProperty<>(categories.get(selected >= 0 && selected < categories.size() ? selected : 0)); - } - - public static void init() { - INSTANCE = new AppPrefs(); - PrefsProvider.getAll().forEach(prov -> prov.addPrefs(INSTANCE.extensionHandler)); - INSTANCE.load(); - - INSTANCE.encryptAllVaultData.addListener((observableValue, aBoolean, t1) -> { - if (DataStorage.get() != null) { - DataStorage.get().forceRewrite(); - } - }); - } - - public static void setDefaults() { - INSTANCE.initDefaultValues(); - PrefsProvider.getAll().forEach(prov -> prov.initDefaultValues()); - } - - public static void reset() { - INSTANCE.save(); - - // Keep instance as we might need some values on shutdown, e.g. on update with terminals - // INSTANCE = null; - } - - public static AppPrefs get() { - return INSTANCE; - } - @SuppressWarnings("unchecked") private T map(T o, String name, Class clazz) { mapping.add(new Mapping<>(name, (Property) o, (Class) clazz)); @@ -425,7 +416,11 @@ public class AppPrefs { } public Comp getCustomComp(String id) { - return customEntries.entrySet().stream().filter(e -> e.getKey().getKey().equals(id)).findFirst().map(Map.Entry::getValue).orElseThrow(); + return customEntries.entrySet().stream() + .filter(e -> e.getKey().getKey().equals(id)) + .findFirst() + .map(Map.Entry::getValue) + .orElseThrow(); } public void load() { @@ -438,13 +433,14 @@ public class AppPrefs { // Overdose is not really needed as many moved properties have changed anyways var isDefault = Objects.equals(r, def); if (isDefault && value.isVaultSpecific()) { - loadValue(globalStorageHandler,value); + loadValue(globalStorageHandler, value); } } } private T loadValue(AppPrefsStorageHandler handler, Mapping value) { - var val = handler.loadObject(value.getKey(), value.getValueClass(), value.getProperty().getValue()); + var val = handler.loadObject( + value.getKey(), value.getValueClass(), value.getProperty().getValue()); value.getProperty().setValue(val); return val; } @@ -475,13 +471,36 @@ public class AppPrefs { return ApplicationHelper.replaceFileArgument(passwordManagerCommand.get(), "KEY", key); } + @Value + public static class Mapping { + + String key; + Property property; + Class valueClass; + boolean vaultSpecific; + + public Mapping(String key, Property property, Class valueClass) { + this.key = key; + this.property = property; + this.valueClass = valueClass; + this.vaultSpecific = false; + } + + public Mapping(String key, Property property, Class valueClass, boolean vaultSpecific) { + this.key = key; + this.property = property; + this.valueClass = valueClass; + this.vaultSpecific = vaultSpecific; + } + } + @Getter private class PrefsHandlerImpl implements PrefsHandler { @Override public void addSetting(String id, Class c, Property property, Comp comp) { var m = new Mapping<>(id, property, c); - customEntries.put(m,comp); + customEntries.put(m, comp); mapping.add(m); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java index 8fb06b585..38caefcd3 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsComp.java @@ -20,7 +20,12 @@ public class AppPrefsComp extends SimpleComp { protected Region createSimple() { var map = AppPrefs.get().getCategories().stream() .collect(Collectors.toMap(appPrefsCategory -> appPrefsCategory, appPrefsCategory -> { - return appPrefsCategory.create().maxWidth(700).padding(new Insets(40, 40, 20, 40)).styleClass("prefs-container").createRegion(); + return appPrefsCategory + .create() + .maxWidth(700) + .padding(new Insets(40, 40, 20, 40)) + .styleClass("prefs-container") + .createRegion(); })); var pfxSp = new ScrollPane(); SimpleChangeListener.apply(AppPrefs.get().getSelectedCategory(), val -> { diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java index 8dd521c98..43de8d9aa 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsSidebarComp.java @@ -19,17 +19,21 @@ public class AppPrefsSidebarComp extends SimpleComp { @Override protected Region createSimple() { - var buttons = AppPrefs.get().getCategories().stream().>map(appPrefsCategory -> { - return new ButtonComp(AppI18n.observable(appPrefsCategory.getId()), () -> { - AppPrefs.get().getSelectedCategory().setValue(appPrefsCategory); - }).apply(struc -> { - struc.get().setTextAlignment(TextAlignment.LEFT); - struc.get().setAlignment(Pos.CENTER_LEFT); - SimpleChangeListener.apply(AppPrefs.get().getSelectedCategory(), val -> { - struc.get().pseudoClassStateChanged(SELECTED,appPrefsCategory.equals(val)); - }); - }).grow(true, false); - }).toList(); + var buttons = AppPrefs.get().getCategories().stream() + .>map(appPrefsCategory -> { + return new ButtonComp(AppI18n.observable(appPrefsCategory.getId()), () -> { + AppPrefs.get().getSelectedCategory().setValue(appPrefsCategory); + }) + .apply(struc -> { + struc.get().setTextAlignment(TextAlignment.LEFT); + struc.get().setAlignment(Pos.CENTER_LEFT); + SimpleChangeListener.apply(AppPrefs.get().getSelectedCategory(), val -> { + struc.get().pseudoClassStateChanged(SELECTED, appPrefsCategory.equals(val)); + }); + }) + .grow(true, false); + }) + .toList(); var vbox = new VerticalComp(buttons).styleClass("sidebar"); vbox.apply(struc -> { SimpleChangeListener.apply(PlatformThread.sync(AppPrefs.get().getSelectedCategory()), val -> { diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java index 1e6f06c71..ff645cc8a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefsStorageHandler.java @@ -21,7 +21,9 @@ public class AppPrefsStorageHandler { private final Path file; private ObjectNode content; - public AppPrefsStorageHandler(Path file) {this.file = file;} + public AppPrefsStorageHandler(Path file) { + this.file = file; + } private JsonNode getContent(String key) { if (content == null) { @@ -46,7 +48,7 @@ public class AppPrefsStorageHandler { } @SuppressWarnings("unchecked") - public T loadObject(String id, Class type, T defaultObject) { + public T loadObject(String id, Class type, T defaultObject) { var tree = getContent(id); if (tree == null) { TrackEvent.debug("Preferences value not found for key: " + id); @@ -58,15 +60,23 @@ public class AppPrefsStorageHandler { if (all != null) { Class cast = (Class) type; var in = tree.asText(); - var found = all.stream().filter(t -> ((PrefsChoiceValue) t).getId().equalsIgnoreCase(in)).findAny(); + var found = all.stream() + .filter(t -> ((PrefsChoiceValue) t).getId().equalsIgnoreCase(in)) + .findAny(); if (found.isEmpty()) { - TrackEvent.withWarn("Invalid prefs value found").tag("key", id).tag("value", in).handle(); + TrackEvent.withWarn("Invalid prefs value found") + .tag("key", id) + .tag("value", in) + .handle(); return defaultObject; } var supported = getSupported(cast); if (!supported.contains(found.get())) { - TrackEvent.withWarn("Unsupported prefs value found").tag("key", id).tag("value", in).handle(); + TrackEvent.withWarn("Unsupported prefs value found") + .tag("key", id) + .tag("value", in) + .handle(); return defaultObject; } diff --git a/app/src/main/java/io/xpipe/app/prefs/AppearanceCategory.java b/app/src/main/java/io/xpipe/app/prefs/AppearanceCategory.java index 0c5f6e065..64ac0da7a 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppearanceCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppearanceCategory.java @@ -22,30 +22,32 @@ public class AppearanceCategory extends AppPrefsCategory { return new OptionsBuilder() .addTitle("uiOptions") .sub(new OptionsBuilder() - .nameAndDescription("theme") - .addComp(ChoiceComp.ofTranslatable(prefs.theme, AppTheme.Theme.ALL, false), prefs.theme) - .nameAndDescription("performanceMode") - .addToggle(prefs.performanceMode) + .nameAndDescription("theme") + .addComp(ChoiceComp.ofTranslatable(prefs.theme, AppTheme.Theme.ALL, false), prefs.theme) + .nameAndDescription("performanceMode") + .addToggle(prefs.performanceMode) .nameAndDescription("uiScale") .addComp(new IntFieldComp(prefs.uiScale).maxWidth(100), prefs.uiScale) - .nameAndDescription("useSystemFont") - .addToggle(prefs.useSystemFont) + .nameAndDescription("useSystemFont") + .addToggle(prefs.useSystemFont) .nameAndDescription("condenseConnectionDisplay") - .addToggle(prefs.condenseConnectionDisplay) - ).addTitle("windowOptions").sub(new OptionsBuilder() + .addToggle(prefs.condenseConnectionDisplay)) + .addTitle("windowOptions") + .sub(new OptionsBuilder() .nameAndDescription("windowOpacity") - .addComp(Comp.of(() -> { - var s = new Slider(0.3, 1.0, prefs.windowOpacity.get()); - s.getStyleClass().add(Styles.SMALL); - prefs.windowOpacity.bind(s.valueProperty()); - s.setSkin(new ProgressSliderSkin(s)); - return s; - }), prefs.windowOpacity) - .nameAndDescription("saveWindowLocation") - .addToggle(prefs.saveWindowLocation) - .nameAndDescription("enforceWindowModality") - .addToggle(prefs.enforceWindowModality) - ) + .addComp( + Comp.of(() -> { + var s = new Slider(0.3, 1.0, prefs.windowOpacity.get()); + s.getStyleClass().add(Styles.SMALL); + prefs.windowOpacity.bind(s.valueProperty()); + s.setSkin(new ProgressSliderSkin(s)); + return s; + }), + prefs.windowOpacity) + .nameAndDescription("saveWindowLocation") + .addToggle(prefs.saveWindowLocation) + .nameAndDescription("enforceWindowModality") + .addToggle(prefs.enforceWindowModality)) .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java b/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java index c876b30e3..325c21ed5 100644 --- a/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java +++ b/app/src/main/java/io/xpipe/app/prefs/CloseBehaviour.java @@ -11,7 +11,6 @@ public enum CloseBehaviour implements PrefsChoiceValue { public void run() { OperationMode.shutdown(false, false); } - }, MINIMIZE_TO_TRAY("app.minimizeToTray") { diff --git a/app/src/main/java/io/xpipe/app/prefs/DeveloperCategory.java b/app/src/main/java/io/xpipe/app/prefs/DeveloperCategory.java index 1b7def71f..08fed8334 100644 --- a/app/src/main/java/io/xpipe/app/prefs/DeveloperCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/DeveloperCategory.java @@ -45,26 +45,25 @@ public class DeveloperCategory extends AppPrefsCategory { }; var runLocalCommand = new HorizontalComp(List.of( - new TextFieldComp(localCommand) - .apply(struc -> struc.get().setPromptText("Local command")) - .styleClass(Styles.LEFT_PILL) - .grow(false, true), - new ButtonComp(null, new FontIcon("mdi2p-play"), test) - .styleClass(Styles.RIGHT_PILL) - .grow(false, true))) - .padding(new Insets(15, 0, 0, 0)) - .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)) - .apply(struc -> struc.get().setFillHeight(true)); + new TextFieldComp(localCommand) + .apply(struc -> struc.get().setPromptText("Local command")) + .styleClass(Styles.LEFT_PILL) + .grow(false, true), + new ButtonComp(null, new FontIcon("mdi2p-play"), test) + .styleClass(Styles.RIGHT_PILL) + .grow(false, true))) + .padding(new Insets(15, 0, 0, 0)) + .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)) + .apply(struc -> struc.get().setFillHeight(true)); return new OptionsBuilder() .addTitle("developer") .sub(new OptionsBuilder() - .nameAndDescription("developerDisableUpdateVersionCheck") - .addToggle(prefs.developerDisableUpdateVersionCheck) - .nameAndDescription("developerDisableGuiRestrictions") - .addToggle(prefs.developerDisableGuiRestrictions) - .nameAndDescription("shellCommandTest") - .addComp(runLocalCommand) - ) + .nameAndDescription("developerDisableUpdateVersionCheck") + .addToggle(prefs.developerDisableUpdateVersionCheck) + .nameAndDescription("developerDisableGuiRestrictions") + .addToggle(prefs.developerDisableGuiRestrictions) + .nameAndDescription("shellCommandTest") + .addComp(runLocalCommand)) .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java b/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java index 920b14113..caefd19ec 100644 --- a/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/EditorCategory.java @@ -27,20 +27,30 @@ public class EditorCategory extends AppPrefsCategory { protected Comp create() { var prefs = AppPrefs.get(); var terminalTest = new StackComp( - List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { - prefs.save(); - ThreadHelper.runFailableAsync(() -> { - var editor = - AppPrefs.get().externalEditor().getValue(); - if (editor != null) { - FileOpener.openReadOnlyString("Test"); - } - }); - }) )).padding(new Insets(15, 0, 0, 0)).apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); - return new OptionsBuilder().addTitle("editorConfiguration").sub(new OptionsBuilder().nameAndDescription("editorProgram").addComp( - ChoiceComp.ofTranslatable(prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false)).nameAndDescription( - "customEditorCommand").addComp(new TextFieldComp(prefs.customEditorCommand, true).apply( - struc -> struc.get().setPromptText("myeditor $FILE")).hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))).addComp( - terminalTest).nameAndDescription("preferEditorTabs").addToggle(prefs.preferEditorTabs)).buildComp(); + List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { + prefs.save(); + ThreadHelper.runFailableAsync(() -> { + var editor = AppPrefs.get().externalEditor().getValue(); + if (editor != null) { + FileOpener.openReadOnlyString("Test"); + } + }); + }))) + .padding(new Insets(15, 0, 0, 0)) + .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); + return new OptionsBuilder() + .addTitle("editorConfiguration") + .sub(new OptionsBuilder() + .nameAndDescription("editorProgram") + .addComp(ChoiceComp.ofTranslatable( + prefs.externalEditor, PrefsChoiceValue.getSupported(ExternalEditorType.class), false)) + .nameAndDescription("customEditorCommand") + .addComp(new TextFieldComp(prefs.customEditorCommand, true) + .apply(struc -> struc.get().setPromptText("myeditor $FILE")) + .hide(prefs.externalEditor.isNotEqualTo(ExternalEditorType.CUSTOM))) + .addComp(terminalTest) + .nameAndDescription("preferEditorTabs") + .addToggle(prefs.preferEditorTabs)) + .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java index 5db743563..9bc5bbf00 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java @@ -23,15 +23,15 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { this.id = id; } + public abstract boolean isAvailable(); + + public abstract boolean isSelectable(); + @Override public String getId() { return id; } - public abstract boolean isSelectable(); - - public abstract boolean isAvailable(); - @Override public String toString() { return getId(); @@ -60,16 +60,21 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { // Check if returned paths are actually valid // Also sort them by length to prevent finding a deeply buried app - var valid = path.lines().filter(s -> { - try { - return Files.exists(Path.of(s)); - } catch (Exception ex) { - return false; - } - }).sorted(Comparator.comparingInt(value -> value.length())).toList(); + var valid = path.lines() + .filter(s -> { + try { + return Files.exists(Path.of(s)); + } catch (Exception ex) { + return false; + } + }) + .sorted(Comparator.comparingInt(value -> value.length())) + .toList(); // Require app in proper applications directory - var app = valid.stream().filter(s -> s.contains("Applications")).findFirst(); + var app = valid.stream() + .filter(s -> s.contains("Applications")) + .findFirst(); return app.map(Path::of); } } catch (Exception e) { @@ -117,7 +122,10 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { } if (ShellDialects.isPowershell(pc)) { - var cmd = CommandBuilder.of().add("Start-Process", "-FilePath").addFile(executable).add("-ArgumentList") + var cmd = CommandBuilder.of() + .add("Start-Process", "-FilePath") + .addFile(executable) + .add("-ArgumentList") .add(pc.getShellDialect().literalArgument(args)); pc.executeSimpleCommand(cmd); return; @@ -148,7 +156,8 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue { protected Optional determineFromPath() { // Try to locate if it is in the Path try (var cc = LocalShell.getShell() - .command(CommandBuilder.ofFunction(var1 -> var1.getShellDialect().getWhichCommand(executable))) + .command(CommandBuilder.ofFunction( + var1 -> var1.getShellDialect().getWhichCommand(executable))) .start()) { var out = cc.readStdoutDiscardErr(); var exit = cc.getExitCode(); diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java index dd86d463a..b8239f3ef 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalEditorType.java @@ -53,10 +53,10 @@ public interface ExternalEditorType extends PrefsChoiceValue { @Override protected Optional determineInstallation() { return Optional.of(Path.of(System.getenv("LOCALAPPDATA")) - .resolve("Programs") - .resolve("Microsoft VS Code Insiders") - .resolve("bin") - .resolve("code-insiders.cmd")); + .resolve("Programs") + .resolve("Microsoft VS Code Insiders") + .resolve("bin") + .resolve("code-insiders.cmd")); } }; @@ -68,14 +68,15 @@ public interface ExternalEditorType extends PrefsChoiceValue { // Check 32 bit install if (found.isEmpty()) { - found = WindowsRegistry.readString(WindowsRegistry.HKEY_LOCAL_MACHINE, "WOW6432Node\\SOFTWARE\\Notepad++", null); + found = WindowsRegistry.readString( + WindowsRegistry.HKEY_LOCAL_MACHINE, "WOW6432Node\\SOFTWARE\\Notepad++", null); } return found.map(p -> p + "\\notepad++.exe").map(Path::of); } }; LinuxPathType VSCODE_LINUX = new LinuxPathType("app.vscode", "code"); - + LinuxPathType VSCODIUM_LINUX = new LinuxPathType("app.vscodium", "codium"); LinuxPathType GNOME = new LinuxPathType("app.gnomeTextEditor", "gnome-text-editor"); @@ -89,36 +90,11 @@ public interface ExternalEditorType extends PrefsChoiceValue { LinuxPathType MOUSEPAD = new LinuxPathType("app.mousepad", "mousepad"); LinuxPathType PLUMA = new LinuxPathType("app.pluma", "pluma"); - - class MacOsEditor extends ExternalApplicationType.MacApplication implements ExternalEditorType { - - public MacOsEditor(String id, String applicationName) { - super(id, applicationName); - } - - @Override - public void launch(Path file) throws Exception { - var execFile = getApplicationPath(); - if (execFile.isEmpty()) { - throw new IOException("Application " + applicationName + ".app not found"); - } - - ApplicationHelper.executeLocalApplication( - CommandBuilder.of().add("open", "-a").addFile(execFile.orElseThrow().toString()).addFile(file.toString()), - false); - } - } - ExternalEditorType TEXT_EDIT = new MacOsEditor("app.textEdit", "TextEdit"); - ExternalEditorType BBEDIT = new MacOsEditor("app.bbedit", "BBEdit"); - ExternalEditorType SUBLIME_MACOS = new MacOsEditor("app.sublime", "Sublime Text"); - ExternalEditorType VSCODE_MACOS = new MacOsEditor("app.vscode", "Visual Studio Code"); - ExternalEditorType VSCODIUM_MACOS = new MacOsEditor("app.vscodium", "VSCodium"); - ExternalEditorType CUSTOM = new ExternalEditorType() { @Override @@ -128,9 +104,11 @@ public interface ExternalEditorType extends PrefsChoiceValue { throw ErrorEvent.unreportable(new IllegalStateException("No custom editor command specified")); } - var format = customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE"; + var format = + customCommand.toLowerCase(Locale.ROOT).contains("$file") ? customCommand : customCommand + " $FILE"; ApplicationHelper.executeLocalApplication( - CommandBuilder.of().add(ApplicationHelper.replaceFileArgument(format, "FILE", file.toString())), true); + CommandBuilder.of().add(ApplicationHelper.replaceFileArgument(format, "FILE", file.toString())), + true); } @Override @@ -138,79 +116,17 @@ public interface ExternalEditorType extends PrefsChoiceValue { return "app.custom"; } }; - - void launch(Path file) throws Exception; - - class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType { - - private final boolean detach; - - public GenericPathType(String id, String command, boolean detach) { - super(id, command); - this.detach = detach; - } - - @Override - public void launch(Path file) throws Exception { - ApplicationHelper.executeLocalApplication(CommandBuilder.of().add(executable).addFile(file.toString()), detach); - } - - @Override - public boolean isSelectable() { - return true; - } - } - - class LinuxPathType extends GenericPathType { - - public LinuxPathType(String id, String command) { - super(id, command, true); - } - - @Override - public boolean isSelectable() { - return OsType.getLocal().equals(OsType.LINUX); - } - } - - abstract class WindowsType extends ExternalApplicationType.WindowsType - implements ExternalEditorType { - - private final boolean detach; - - public WindowsType(String id, String executable, boolean detach) { - super(id, executable); - this.detach = detach; - } - - @Override - public void launch(Path file) throws Exception { - var location = determineFromPath(); - if (location.isEmpty()) { - location = determineInstallation(); - if (location.isEmpty()) { - throw ErrorEvent.unreportable(new IOException("Unable to find installation of " + toTranslatedString())); - } - } - - Optional finalLocation = location; - ApplicationHelper.executeLocalApplication( - CommandBuilder.of().addFile(finalLocation.get().toString()).addFile(file.toString()), - detach); - } - } - ExternalEditorType FLEET = new GenericPathType("app.fleet", "fleet", false); ExternalEditorType INTELLIJ = new GenericPathType("app.intellij", "idea", false); ExternalEditorType PYCHARM = new GenericPathType("app.pycharm", "pycharm", false); ExternalEditorType WEBSTORM = new GenericPathType("app.webstorm", "webstorm", false); ExternalEditorType CLION = new GenericPathType("app.clion", "clion", false); - - List WINDOWS_EDITORS = List.of(VSCODIUM_WINDOWS, VSCODE_INSIDERS_WINDOWS, VSCODE_WINDOWS, NOTEPADPLUSPLUS, NOTEPAD); - List LINUX_EDITORS = List.of(VSCODIUM_LINUX, VSCODE_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD, GNOME); + List WINDOWS_EDITORS = + List.of(VSCODIUM_WINDOWS, VSCODE_INSIDERS_WINDOWS, VSCODE_WINDOWS, NOTEPADPLUSPLUS, NOTEPAD); + List LINUX_EDITORS = + List.of(VSCODIUM_LINUX, VSCODE_LINUX, KATE, GEDIT, PLUMA, LEAFPAD, MOUSEPAD, GNOME); List MACOS_EDITORS = List.of(BBEDIT, VSCODIUM_MACOS, VSCODE_MACOS, SUBLIME_MACOS, TEXT_EDIT); List CROSS_PLATFORM_EDITORS = List.of(FLEET, INTELLIJ, PYCHARM, WEBSTORM, CLION); - @SuppressWarnings("TrivialFunctionalExpressionUsage") List ALL = ((Supplier>) () -> { var all = new ArrayList(); @@ -267,4 +183,87 @@ public interface ExternalEditorType extends PrefsChoiceValue { .orElse(null)); } } + + void launch(Path file) throws Exception; + + class MacOsEditor extends ExternalApplicationType.MacApplication implements ExternalEditorType { + + public MacOsEditor(String id, String applicationName) { + super(id, applicationName); + } + + @Override + public void launch(Path file) throws Exception { + var execFile = getApplicationPath(); + if (execFile.isEmpty()) { + throw new IOException("Application " + applicationName + ".app not found"); + } + + ApplicationHelper.executeLocalApplication( + CommandBuilder.of() + .add("open", "-a") + .addFile(execFile.orElseThrow().toString()) + .addFile(file.toString()), + false); + } + } + + class GenericPathType extends ExternalApplicationType.PathApplication implements ExternalEditorType { + + private final boolean detach; + + public GenericPathType(String id, String command, boolean detach) { + super(id, command); + this.detach = detach; + } + + @Override + public void launch(Path file) throws Exception { + ApplicationHelper.executeLocalApplication( + CommandBuilder.of().add(executable).addFile(file.toString()), detach); + } + + @Override + public boolean isSelectable() { + return true; + } + } + + class LinuxPathType extends GenericPathType { + + public LinuxPathType(String id, String command) { + super(id, command, true); + } + + @Override + public boolean isSelectable() { + return OsType.getLocal().equals(OsType.LINUX); + } + } + + abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalEditorType { + + private final boolean detach; + + public WindowsType(String id, String executable, boolean detach) { + super(id, executable); + this.detach = detach; + } + + @Override + public void launch(Path file) throws Exception { + var location = determineFromPath(); + if (location.isEmpty()) { + location = determineInstallation(); + if (location.isEmpty()) { + throw ErrorEvent.unreportable( + new IOException("Unable to find installation of " + toTranslatedString())); + } + } + + Optional finalLocation = location; + ApplicationHelper.executeLocalApplication( + CommandBuilder.of().addFile(finalLocation.get().toString()).addFile(file.toString()), detach); + } + } } diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java index e4ad23809..609d36583 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalTerminalType.java @@ -26,21 +26,17 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } @Override - protected CommandBuilder toCommand(LaunchConfiguration configuration) { - if (configuration.getScriptDialect().equals(ShellDialects.CMD)) { - return CommandBuilder.of() - .add("/c") - .add(configuration.getScriptFile()); - } - - return CommandBuilder.of() - .add("/c") - .add(configuration.getDialectLaunchCommand()); + public boolean supportsColoredTitle() { + return false; } @Override - public boolean supportsColoredTitle() { - return false; + protected CommandBuilder toCommand(LaunchConfiguration configuration) { + if (configuration.getScriptDialect().equals(ShellDialects.CMD)) { + return CommandBuilder.of().add("/c").add(configuration.getScriptFile()); + } + + return CommandBuilder.of().add("/c").add(configuration.getDialectLaunchCommand()); } }; @@ -65,9 +61,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .add(configuration.getScriptFile()); } - return CommandBuilder.of() - .add("-Command") - .add(configuration.getDialectLaunchCommand()); + return CommandBuilder.of().add("-Command").add(configuration.getDialectLaunchCommand()); } }; @@ -93,7 +87,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } // Fix for https://github.com/PowerShell/PowerShell/issues/18530#issuecomment-1325691850 - var script = ScriptHelper.createLocalExecScript("set \"PSModulePath=\"\r\n& \"" + configuration.getScriptFile() + "\""); + var script = ScriptHelper.createLocalExecScript( + "set \"PSModulePath=\"\r\n& \"" + configuration.getScriptFile() + "\""); return CommandBuilder.of() .add("-Command") .add(configuration.withScriptFile(script).getDialectLaunchCommand()); @@ -123,7 +118,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { private Path getPath() { var local = System.getenv("LOCALAPPDATA"); - return Path.of(local).resolve("Microsoft\\WindowsApps\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\wt.exe"); + return Path.of(local) + .resolve("Microsoft\\WindowsApps\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\wt.exe"); } @Override @@ -150,13 +146,15 @@ public interface ExternalTerminalType extends PrefsChoiceValue { // backslash of a filepath to escape the closing quote in the title argument // So just remove that slash var fixedName = FileNames.removeTrailingSlash(configuration.getColoredTitle()); - var toExec = !ShellDialects.isPowershell(LocalShell.getShell()) ? - CommandBuilder.of().addFile(configuration.getScriptFile()) : - CommandBuilder.of().add("powershell", "-ExecutionPolicy", "Bypass", "-File").addQuoted(configuration.getScriptFile()); + var toExec = !ShellDialects.isPowershell(LocalShell.getShell()) + ? CommandBuilder.of().addFile(configuration.getScriptFile()) + : CommandBuilder.of() + .add("powershell", "-ExecutionPolicy", "Bypass", "-File") + .addQuoted(configuration.getScriptFile()); return CommandBuilder.of() - .add("-w", "1", "nt", "--title") - .addQuoted(fixedName) - .add(toExec); + .add("-w", "1", "nt", "--title") + .addQuoted(fixedName) + .add(toExec); } }; @@ -180,36 +178,14 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addQuoted("colors.primary.background='%s'" .formatted(configuration.getColor().toHexString())); } - return b.add("-t").addQuoted(configuration.getCleanTitle()) - .add("-e") - .add("cmd") - .add("/c") - .addQuoted(configuration.getScriptFile().replaceAll(" ", "^$0")); + return b.add("-t") + .addQuoted(configuration.getCleanTitle()) + .add("-e") + .add("cmd") + .add("/c") + .addQuoted(configuration.getScriptFile().replaceAll(" ", "^$0")); } }; - - abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalTerminalType { - - public WindowsType(String id, String executable) { - super(id, executable); - } - - @Override - public void launch(LaunchConfiguration configuration) throws Exception { - var location = determineFromPath(); - if (location.isEmpty()) { - location = determineInstallation(); - if (location.isEmpty()) { - throw new IOException("Unable to find installation of " + toTranslatedString()); - } - } - - execute(location.get(), configuration); - } - - protected abstract void execute(Path file, LaunchConfiguration configuration) throws Exception; - } - ExternalTerminalType TABBY_WINDOWS = new WindowsType("app.tabby", "Tabby") { @Override @@ -230,7 +206,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { WindowsRegistry.HKEY_CURRENT_USER, "SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5", "InstallLocation") - .map(p -> p + "\\Tabby.exe").map(Path::of); + .map(p -> p + "\\Tabby.exe") + .map(Path::of); if (perUser.isPresent()) { return perUser; } @@ -239,37 +216,36 @@ public interface ExternalTerminalType extends PrefsChoiceValue { WindowsRegistry.HKEY_LOCAL_MACHINE, "SOFTWARE\\71445fac-d6ef-5436-9da7-5a323762d7f5", "InstallLocation") - .map(p -> p + "\\Tabby.exe").map(Path::of); + .map(p -> p + "\\Tabby.exe") + .map(Path::of); return systemWide; } }; + ExternalTerminalType WEZ_WINDOWS = new WindowsType("app.wezterm", "wezterm-gui") { - ExternalTerminalType WEZ_WINDOWS = new WindowsType("app.wezterm", "wezterm-gui") { + @Override + public boolean supportsTabs() { + return false; + } - @Override - public boolean supportsTabs() { - return false; - } - - @Override - protected void execute(Path file, LaunchConfiguration configuration) throws Exception { - ApplicationHelper.executeLocalApplication( - CommandBuilder.of().addFile(file.toString()).add("start").addFile(configuration.getScriptFile()), - true); - } - - @Override - protected Optional determineInstallation() { - Optional launcherDir; - launcherDir = WindowsRegistry.readString( - WindowsRegistry.HKEY_LOCAL_MACHINE, - "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1", - "InstallLocation") - .map(p -> p + "\\wezterm-gui.exe"); - return launcherDir.map(Path::of); - } - }; + @Override + protected void execute(Path file, LaunchConfiguration configuration) throws Exception { + ApplicationHelper.executeLocalApplication( + CommandBuilder.of().addFile(file.toString()).add("start").addFile(configuration.getScriptFile()), + true); + } + @Override + protected Optional determineInstallation() { + Optional launcherDir; + launcherDir = WindowsRegistry.readString( + WindowsRegistry.HKEY_LOCAL_MACHINE, + "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{BCF6F0DA-5B9A-408D-8562-F680AE6E1EAF}_is1", + "InstallLocation") + .map(p -> p + "\\wezterm-gui.exe"); + return launcherDir.map(Path::of); + } + }; ExternalTerminalType WEZ_LINUX = new SimplePathType("app.wezterm", "wezterm-gui") { @Override @@ -282,7 +258,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("start").addFile(configuration.getScriptFile()); } }; - ExternalTerminalType GNOME_TERMINAL = new PathCheckType("app.gnomeTerminal", "gnome-terminal") { @Override @@ -293,7 +268,8 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override public void launch(LaunchConfiguration configuration) throws Exception { try (ShellControl pc = LocalShell.getShell()) { - ApplicationHelper.checkIsInPath(pc, executable, toTranslatedString().getValue(), null); + ApplicationHelper.checkIsInPath( + pc, executable, toTranslatedString().getValue(), null); var toExecute = CommandBuilder.of() .add(executable, "-v", "--title") @@ -308,7 +284,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } }; - ExternalTerminalType KONSOLE = new SimplePathType("app.konsole", "konsole") { @Override @@ -329,7 +304,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("--new-tab", "-e").addFile(configuration.getScriptFile()); } }; - ExternalTerminalType XFCE = new SimplePathType("app.xfce", "xfce4-terminal") { @Override @@ -346,7 +320,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addFile(configuration.getScriptFile()); } }; - ExternalTerminalType ELEMENTARY = new SimplePathType("app.elementaryTerminal", "io.elementary.terminal") { @Override @@ -359,7 +332,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("--new-tab").add("-e").addFile(configuration.getColoredTitle()); } }; - ExternalTerminalType TILIX = new SimplePathType("app.tilix", "tilix") { @Override @@ -369,10 +341,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(LaunchConfiguration configuration) { - return CommandBuilder.of().add("-t").addQuoted(configuration.getColoredTitle()).add("-e").addFile(configuration.getScriptFile()); + return CommandBuilder.of() + .add("-t") + .addQuoted(configuration.getColoredTitle()) + .add("-e") + .addFile(configuration.getScriptFile()); } }; - ExternalTerminalType TERMINATOR = new SimplePathType("app.terminator", "terminator") { @Override @@ -390,7 +365,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .add("--new-tab"); } }; - ExternalTerminalType KITTY_LINUX = new SimplePathType("app.kitty", "kitty") { @Override @@ -400,10 +374,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(LaunchConfiguration configuration) { - return CommandBuilder.of().add("-1").add("-T").addQuoted(configuration.getColoredTitle()).addQuoted(configuration.getScriptFile()); + return CommandBuilder.of() + .add("-1") + .add("-T") + .addQuoted(configuration.getColoredTitle()) + .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType TERMINOLOGY = new SimplePathType("app.terminology", "terminology") { @Override @@ -421,7 +398,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType COOL_RETRO_TERM = new SimplePathType("app.coolRetroTerm", "cool-retro-term") { @Override @@ -431,10 +407,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(LaunchConfiguration configuration) { - return CommandBuilder.of().add("-T").addQuoted(configuration.getColoredTitle()).add("-e").addQuoted(configuration.getScriptFile()); + return CommandBuilder.of() + .add("-T") + .addQuoted(configuration.getColoredTitle()) + .add("-e") + .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType GUAKE = new SimplePathType("app.guake", "guake") { @Override @@ -452,7 +431,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType ALACRITTY_LINUX = new SimplePathType("app.alacritty", "alacritty") { @Override @@ -467,10 +445,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(LaunchConfiguration configuration) { - return CommandBuilder.of().add("-t").addQuoted(configuration.getCleanTitle()).add("-e").addQuoted(configuration.getScriptFile()); + return CommandBuilder.of() + .add("-t") + .addQuoted(configuration.getCleanTitle()) + .add("-e") + .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType TILDA = new SimplePathType("app.tilda", "tilda") { @Override @@ -483,7 +464,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("-c").addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType XTERM = new SimplePathType("app.xterm", "xterm") { @Override @@ -493,10 +473,13 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override protected CommandBuilder toCommand(LaunchConfiguration configuration) { - return CommandBuilder.of().add("-title").addQuoted(configuration.getColoredTitle()).add("-e").addQuoted(configuration.getScriptFile()); + return CommandBuilder.of() + .add("-title") + .addQuoted(configuration.getColoredTitle()) + .add("-e") + .addQuoted(configuration.getScriptFile()); } }; - ExternalTerminalType DEEPIN_TERMINAL = new SimplePathType("app.deepinTerminal", "deepin-terminal") { @Override @@ -509,8 +492,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("-C").addQuoted(configuration.getScriptFile()); } }; - - ExternalTerminalType Q_TERMINAL = new SimplePathType("app.qTerminal", "qterminal") { @Override @@ -523,7 +504,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { return CommandBuilder.of().add("-e").addQuoted(configuration.getColoredTitle()); } }; - ExternalTerminalType MACOS_TERMINAL = new MacOsType("app.macosTerminal", "Terminal") { @Override public boolean supportsTabs() { @@ -545,7 +525,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } }; - ExternalTerminalType ITERM2 = new MacOsType("app.iterm2", "iTerm") { @Override public boolean supportsTabs() { @@ -582,7 +561,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } }; - ExternalTerminalType WARP = new MacOsType("app.warp", "Warp") { @Override public boolean supportsTabs() { @@ -619,7 +597,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } }; - ExternalTerminalType TABBY_MAC_OS = new MacOsType("app.tabby", "Tabby") { @Override public boolean supportsTabs() { @@ -636,7 +613,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addFile(configuration.getScriptFile())); } }; - ExternalTerminalType ALACRITTY_MACOS = new MacOsType("app.alacritty", "Alacritty") { @Override @@ -661,7 +637,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .addFile(configuration.getScriptFile())); } }; - ExternalTerminalType WEZ_MACOS = new MacOsType("app.wezterm", "WezTerm") { @Override @@ -672,15 +647,19 @@ public interface ExternalTerminalType extends PrefsChoiceValue { @Override public void launch(LaunchConfiguration configuration) throws Exception { var c = CommandBuilder.of() - .addFile(getApplicationPath().orElseThrow().resolve("Contents").resolve("MacOS") - .resolve("wezterm-gui").toString()) - .add("start") - .addFile(configuration.getScriptFile()).buildString(LocalShell.getShell()); + .addFile(getApplicationPath() + .orElseThrow() + .resolve("Contents") + .resolve("MacOS") + .resolve("wezterm-gui") + .toString()) + .add("start") + .addFile(configuration.getScriptFile()) + .buildString(LocalShell.getShell()); c = ApplicationHelper.createDetachCommand(LocalShell.getShell(), c); LocalShell.getShell().executeSimpleCommand(c); } }; - ExternalTerminalType KITTY_MACOS = new MacOsType("app.kitty", "kitty") { @Override @@ -722,15 +701,15 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } }; - ExternalTerminalType CUSTOM = new CustomType(); - List WINDOWS_TERMINALS = List.of( TABBY_WINDOWS, ALACRITTY_WINDOWS, WEZ_WINDOWS, WINDOWS_TERMINAL_PREVIEW, - WINDOWS_TERMINAL, PWSH, POWERSHELL, + WINDOWS_TERMINAL, + PWSH, + POWERSHELL, CMD); List LINUX_TERMINALS = List.of( WEZ_LINUX, @@ -739,40 +718,37 @@ public interface ExternalTerminalType extends PrefsChoiceValue { ELEMENTARY, GNOME_TERMINAL, TILIX, - TERMINATOR, KITTY_LINUX, + TERMINATOR, + KITTY_LINUX, TERMINOLOGY, COOL_RETRO_TERM, - GUAKE, ALACRITTY_LINUX, + GUAKE, + ALACRITTY_LINUX, TILDA, XTERM, DEEPIN_TERMINAL, Q_TERMINAL); - List MACOS_TERMINALS = List.of( - ITERM2, - TABBY_MAC_OS, - ALACRITTY_MACOS, - KITTY_MACOS, - WARP, - WEZ_MACOS, - MACOS_TERMINAL); + List MACOS_TERMINALS = + List.of(ITERM2, TABBY_MAC_OS, ALACRITTY_MACOS, KITTY_MACOS, WARP, WEZ_MACOS, MACOS_TERMINAL); @SuppressWarnings("TrivialFunctionalExpressionUsage") List ALL = ((Supplier>) () -> { - var all = new ArrayList(); - if (OsType.getLocal().equals(OsType.WINDOWS)) { - all.addAll(WINDOWS_TERMINALS); - } - if (OsType.getLocal().equals(OsType.LINUX)) { - all.addAll(LINUX_TERMINALS); - } - if (OsType.getLocal().equals(OsType.MACOS)) { - all.addAll(MACOS_TERMINALS); - } - // Prefer with tabs - all.sort(Comparator.comparingInt(o -> (o.supportsTabs() ? -1 : 0))); - all.add(CUSTOM); - return all; - }).get(); + var all = new ArrayList(); + if (OsType.getLocal().equals(OsType.WINDOWS)) { + all.addAll(WINDOWS_TERMINALS); + } + if (OsType.getLocal().equals(OsType.LINUX)) { + all.addAll(LINUX_TERMINALS); + } + if (OsType.getLocal().equals(OsType.MACOS)) { + all.addAll(MACOS_TERMINALS); + } + // Prefer with tabs + all.sort(Comparator.comparingInt(o -> (o.supportsTabs() ? -1 : 0))); + all.add(CUSTOM); + return all; + }) + .get(); static ExternalTerminalType determineDefault() { return ALL.stream() @@ -782,13 +758,49 @@ public interface ExternalTerminalType extends PrefsChoiceValue { .orElse(null); } + boolean supportsTabs(); + + default boolean supportsColoredTitle() { + return true; + } + + default boolean shouldClear() { + return true; + } + + default void launch(LaunchConfiguration configuration) throws Exception {} + + abstract class WindowsType extends ExternalApplicationType.WindowsType implements ExternalTerminalType { + + public WindowsType(String id, String executable) { + super(id, executable); + } + + @Override + public void launch(LaunchConfiguration configuration) throws Exception { + var location = determineFromPath(); + if (location.isEmpty()) { + location = determineInstallation(); + if (location.isEmpty()) { + throw new IOException("Unable to find installation of " + toTranslatedString()); + } + } + + execute(location.get(), configuration); + } + + protected abstract void execute(Path file, LaunchConfiguration configuration) throws Exception; + } + @Value class LaunchConfiguration { DataStoreColor color; String coloredTitle; String cleanTitle; + @With String scriptFile; + ShellDialect scriptDialect; public CommandBuilder getDialectLaunchCommand() { @@ -803,17 +815,6 @@ public interface ExternalTerminalType extends PrefsChoiceValue { } } - boolean supportsTabs(); - - default boolean supportsColoredTitle() { - return true; - } - default boolean shouldClear() { - return true; - } - - default void launch(LaunchConfiguration configuration) throws Exception {} - class CustomType extends ExternalApplicationType implements ExternalTerminalType { public CustomType() { diff --git a/app/src/main/java/io/xpipe/app/prefs/LocalShellCategory.java b/app/src/main/java/io/xpipe/app/prefs/LocalShellCategory.java index 91c261228..b51696f61 100644 --- a/app/src/main/java/io/xpipe/app/prefs/LocalShellCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/LocalShellCategory.java @@ -18,12 +18,11 @@ public class LocalShellCategory extends AppPrefsCategory { return new OptionsBuilder() .addTitle("localShell") .sub(new OptionsBuilder() - .nameAndDescription("useBundledTools") - .addToggle(prefs.useBundledTools) - .hide(new SimpleBooleanProperty(!OsType.getLocal().equals(OsType.WINDOWS))) - .nameAndDescription("useLocalFallbackShell") - .addToggle(prefs.useLocalFallbackShell) - ) + .nameAndDescription("useBundledTools") + .addToggle(prefs.useBundledTools) + .hide(new SimpleBooleanProperty(!OsType.getLocal().equals(OsType.WINDOWS))) + .nameAndDescription("useLocalFallbackShell") + .addToggle(prefs.useLocalFallbackShell)) .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java index 110a47ed2..9015318d2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/PasswordManagerCategory.java @@ -49,20 +49,24 @@ public class PasswordManagerCategory extends AppPrefsCategory { }; var testPasswordManager = new HorizontalComp(List.of( - new TextFieldComp(testPasswordManagerValue) - .apply(struc -> struc.get().setPromptText("Enter password key")) - .styleClass(Styles.LEFT_PILL) - .grow(false, true), - new ButtonComp(null, new FontIcon("mdi2p-play"), test) - .styleClass(Styles.RIGHT_PILL) - .grow(false, true))) - .padding(new Insets(15, 0, 0, 0)) - .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)) - .apply(struc -> struc.get().setFillHeight(true)); - return new OptionsBuilder().addTitle("passwordManager").sub(new OptionsBuilder() - .nameAndDescription("passwordManagerCommand") - .addComp(new TextFieldComp(prefs.passwordManagerCommand, true).apply(struc -> struc.get().setPromptText("mypassmgr get $KEY"))) - .nameAndDescription("passwordManagerCommandTest") - .addComp(testPasswordManager)).buildComp(); + new TextFieldComp(testPasswordManagerValue) + .apply(struc -> struc.get().setPromptText("Enter password key")) + .styleClass(Styles.LEFT_PILL) + .grow(false, true), + new ButtonComp(null, new FontIcon("mdi2p-play"), test) + .styleClass(Styles.RIGHT_PILL) + .grow(false, true))) + .padding(new Insets(15, 0, 0, 0)) + .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)) + .apply(struc -> struc.get().setFillHeight(true)); + return new OptionsBuilder() + .addTitle("passwordManager") + .sub(new OptionsBuilder() + .nameAndDescription("passwordManagerCommand") + .addComp(new TextFieldComp(prefs.passwordManagerCommand, true) + .apply(struc -> struc.get().setPromptText("mypassmgr get $KEY"))) + .nameAndDescription("passwordManagerCommandTest") + .addComp(testPasswordManager)) + .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java b/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java index 396955ce1..c3b4f8756 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java @@ -28,8 +28,7 @@ public class SecurityCategory extends AppPrefsCategory { .nameAndDescription("dontAutomaticallyStartVmSshServer") .addToggle(prefs.dontAutomaticallyStartVmSshServer) .nameAndDescription("disableTerminalRemotePasswordPreparation") - .addToggle(prefs.disableTerminalRemotePasswordPreparation) - ); + .addToggle(prefs.disableTerminalRemotePasswordPreparation)); return builder.buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/StartupBehaviour.java b/app/src/main/java/io/xpipe/app/prefs/StartupBehaviour.java index 53460b835..48f2471b3 100644 --- a/app/src/main/java/io/xpipe/app/prefs/StartupBehaviour.java +++ b/app/src/main/java/io/xpipe/app/prefs/StartupBehaviour.java @@ -9,8 +9,7 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum StartupBehaviour implements PrefsChoiceValue { - GUI("app.startGui", XPipeDaemonMode.GUI) { - }, + GUI("app.startGui", XPipeDaemonMode.GUI) {}, TRAY("app.startInTray", XPipeDaemonMode.TRAY) { public boolean isSelectable() { diff --git a/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java b/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java index 06e76968c..75f674da2 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SyncCategory.java @@ -9,24 +9,26 @@ import io.xpipe.app.util.OptionsBuilder; public class SyncCategory extends AppPrefsCategory { - public Comp create() { - var prefs = AppPrefs.get(); - var builder = new OptionsBuilder(); - builder.addTitle("sync").sub(new OptionsBuilder().nameAndDescription("enableGitStorage") - .addToggle(prefs.enableGitStorage) - .nameAndDescription("storageGitRemote") - .addString(prefs.storageGitRemote, true) - .disable(prefs.enableGitStorage.not()) - .addComp(prefs.getCustomComp("gitVaultIdentityStrategy")) - .nameAndDescription("openDataDir") - .addComp(new ButtonComp(AppI18n.observable("openDataDirButton"), () -> { - DesktopHelper.browsePath(DataStorage.get().getDataDir()); - }))); - return builder.buildComp(); - } - @Override protected String getId() { return "sync"; } + + public Comp create() { + var prefs = AppPrefs.get(); + var builder = new OptionsBuilder(); + builder.addTitle("sync") + .sub(new OptionsBuilder() + .nameAndDescription("enableGitStorage") + .addToggle(prefs.enableGitStorage) + .nameAndDescription("storageGitRemote") + .addString(prefs.storageGitRemote, true) + .disable(prefs.enableGitStorage.not()) + .addComp(prefs.getCustomComp("gitVaultIdentityStrategy")) + .nameAndDescription("openDataDir") + .addComp(new ButtonComp(AppI18n.observable("openDataDirButton"), () -> { + DesktopHelper.browsePath(DataStorage.get().getDataDir()); + }))); + return builder.buildComp(); + } } diff --git a/app/src/main/java/io/xpipe/app/prefs/SystemCategory.java b/app/src/main/java/io/xpipe/app/prefs/SystemCategory.java index 713a77f71..9a52e8402 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SystemCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SystemCategory.java @@ -18,17 +18,23 @@ public class SystemCategory extends AppPrefsCategory { builder.addTitle("appBehaviour") .sub(new OptionsBuilder() .nameAndDescription("startupBehaviour") - .addComp(ChoiceComp.ofTranslatable(prefs.startupBehaviour, PrefsChoiceValue.getSupported(StartupBehaviour.class), false).minWidth(300)) + .addComp(ChoiceComp.ofTranslatable( + prefs.startupBehaviour, + PrefsChoiceValue.getSupported(StartupBehaviour.class), + false) + .minWidth(300)) .nameAndDescription("closeBehaviour") - .addComp(ChoiceComp.ofTranslatable(prefs.closeBehaviour, PrefsChoiceValue.getSupported(CloseBehaviour.class), false).minWidth(300)) - ) + .addComp(ChoiceComp.ofTranslatable( + prefs.closeBehaviour, + PrefsChoiceValue.getSupported(CloseBehaviour.class), + false) + .minWidth(300))) .addTitle("advanced") - .sub(new OptionsBuilder() - .nameAndDescription("developerMode") - .addToggle(prefs.developerMode)) + .sub(new OptionsBuilder().nameAndDescription("developerMode").addToggle(prefs.developerMode)) .addTitle("updates") .sub(new OptionsBuilder() - .nameAndDescription("automaticallyUpdate").addToggle(prefs.automaticallyCheckForUpdates)); + .nameAndDescription("automaticallyUpdate") + .addToggle(prefs.automaticallyCheckForUpdates)); return builder.buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java index fdc7a4ad7..3bf515190 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TerminalCategory.java @@ -28,31 +28,54 @@ public class TerminalCategory extends AppPrefsCategory { @Override protected Comp create() { var prefs = AppPrefs.get(); - var terminalTest = new StackComp(List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { - prefs.save(); - ThreadHelper.runFailableAsync(() -> { - var term = AppPrefs.get().terminalType().getValue(); - if (term != null) { - TerminalLauncher.open("Test", new LocalStore().control().command("echo Test")); - } - }); - }))).padding(new Insets(15, 0, 0, 0)).apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); - return new OptionsBuilder().addTitle("terminalConfiguration").sub(new OptionsBuilder() - .nameAndDescription("terminalEmulator").addComp( - ChoiceComp.ofTranslatable(prefs.terminalType, PrefsChoiceValue.getSupported(ExternalTerminalType.class), false)) - .nameAndDescription( - "customTerminalCommand").addComp(new TextFieldComp(prefs.customTerminalCommand, true).apply( - struc -> struc.get().setPromptText("myterminal -e $CMD")).hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM))).addComp( - terminalTest) + var terminalTest = new StackComp( + List.of(new ButtonComp(AppI18n.observable("test"), new FontIcon("mdi2p-play"), () -> { + prefs.save(); + ThreadHelper.runFailableAsync(() -> { + var term = AppPrefs.get().terminalType().getValue(); + if (term != null) { + TerminalLauncher.open( + "Test", new LocalStore().control().command("echo Test")); + } + }); + }))) + .padding(new Insets(15, 0, 0, 0)) + .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); + return new OptionsBuilder() + .addTitle("terminalConfiguration") + .sub(new OptionsBuilder() + .nameAndDescription("terminalEmulator") + .addComp(ChoiceComp.ofTranslatable( + prefs.terminalType, PrefsChoiceValue.getSupported(ExternalTerminalType.class), false)) + .nameAndDescription("customTerminalCommand") + .addComp(new TextFieldComp(prefs.customTerminalCommand, true) + .apply(struc -> struc.get().setPromptText("myterminal -e $CMD")) + .hide(prefs.terminalType.isNotEqualTo(ExternalTerminalType.CUSTOM))) + .addComp(terminalTest) .name("preferTerminalTabs") - .description(Bindings.createStringBinding(() -> { - var disabled = prefs.terminalType().getValue() != null && !prefs.terminalType.get().supportsTabs(); - return !disabled ? AppI18n.get("preferTerminalTabs") : - AppI18n.get("preferTerminalTabsDisabled", prefs.terminalType().getValue().toTranslatedString().getValue()); - }, prefs.terminalType())) - .addToggle(prefs.preferTerminalTabs).disable(Bindings.createBooleanBinding(() -> { - return prefs.terminalType().getValue() != null && !prefs.terminalType.get().supportsTabs(); - }, prefs.terminalType())) - .nameAndDescription("clearTerminalOnInit").addToggle(prefs.clearTerminalOnInit)).buildComp(); + .description(Bindings.createStringBinding( + () -> { + var disabled = prefs.terminalType().getValue() != null + && !prefs.terminalType.get().supportsTabs(); + return !disabled + ? AppI18n.get("preferTerminalTabs") + : AppI18n.get( + "preferTerminalTabsDisabled", + prefs.terminalType() + .getValue() + .toTranslatedString() + .getValue()); + }, + prefs.terminalType())) + .addToggle(prefs.preferTerminalTabs) + .disable(Bindings.createBooleanBinding( + () -> { + return prefs.terminalType().getValue() != null + && !prefs.terminalType.get().supportsTabs(); + }, + prefs.terminalType())) + .nameAndDescription("clearTerminalOnInit") + .addToggle(prefs.clearTerminalOnInit)) + .buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java index b142c9621..d9838aa78 100644 --- a/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/TroubleshootCategory.java @@ -26,32 +26,30 @@ public class TroubleshootCategory extends AppPrefsCategory { .spacer(30) .addComp( new TileButtonComp("reportIssue", "reportIssueDescription", "mdal-bug_report", e -> { - var event = ErrorEvent.fromMessage("User Report"); - if (AppLogs.get().isWriteToFile()) { - event.attachment(AppLogs.get().getSessionLogsDirectory()); - } - UserReportComp.show(event.build()); - e.consume(); - }) + var event = ErrorEvent.fromMessage("User Report"); + if (AppLogs.get().isWriteToFile()) { + event.attachment(AppLogs.get().getSessionLogsDirectory()); + } + UserReportComp.show(event.build()); + e.consume(); + }) .grow(true, false), null) .separator() .addComp( new TileButtonComp("launchDebugMode", "launchDebugModeDescription", "mdmz-refresh", e -> { - OperationMode.executeAfterShutdown(() -> { - try (var sc = new LocalStore() - .control() - .start()) { - var script = FileNames.join( - XPipeInstallation.getCurrentInstallationBasePath() - .toString(), - XPipeInstallation.getDaemonDebugScriptPath(OsType.getLocal())); - var runScript = sc.getShellDialect().runScriptCommand(sc, script); - TerminalLauncher.openDirect("XPipe Debug", sc, runScript); - } - }); - e.consume(); - }) + OperationMode.executeAfterShutdown(() -> { + try (var sc = new LocalStore().control().start()) { + var script = FileNames.join( + XPipeInstallation.getCurrentInstallationBasePath() + .toString(), + XPipeInstallation.getDaemonDebugScriptPath(OsType.getLocal())); + var runScript = sc.getShellDialect().runScriptCommand(sc, script); + TerminalLauncher.openDirect("XPipe Debug", sc, runScript); + } + }); + e.consume(); + }) .grow(true, false), null) .separator(); @@ -59,16 +57,16 @@ public class TroubleshootCategory extends AppPrefsCategory { if (AppLogs.get().isWriteToFile()) { b.addComp( new TileButtonComp( - "openCurrentLogFile", - "openCurrentLogFileDescription", - "mdmz-text_snippet", - e -> { - FileOpener.openInTextEditor(AppLogs.get() - .getSessionLogsDirectory() - .resolve("xpipe.log") - .toString()); - e.consume(); - }) + "openCurrentLogFile", + "openCurrentLogFileDescription", + "mdmz-text_snippet", + e -> { + FileOpener.openInTextEditor(AppLogs.get() + .getSessionLogsDirectory() + .resolve("xpipe.log") + .toString()); + e.consume(); + }) .grow(true, false), null) .separator(); @@ -76,22 +74,22 @@ public class TroubleshootCategory extends AppPrefsCategory { b.addComp( new TileButtonComp( - "openInstallationDirectory", - "openInstallationDirectoryDescription", - "mdomz-snippet_folder", - e -> { - DesktopHelper.browsePath( - XPipeInstallation.getCurrentInstallationBasePath()); - e.consume(); - }) + "openInstallationDirectory", + "openInstallationDirectoryDescription", + "mdomz-snippet_folder", + e -> { + DesktopHelper.browsePath( + XPipeInstallation.getCurrentInstallationBasePath()); + e.consume(); + }) .grow(true, false), null) .separator() .addComp( new TileButtonComp("clearCaches", "clearCachesDescription", "mdi2t-trash-can-outline", e -> { - ClearCacheAlert.show(); - e.consume(); - }) + ClearCacheAlert.show(); + e.consume(); + }) .grow(true, false), null); return b.buildComp(); diff --git a/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java b/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java index 1355d4d5c..ac3979cad 100644 --- a/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java +++ b/app/src/main/java/io/xpipe/app/prefs/UpdateCheckComp.java @@ -45,12 +45,15 @@ public class UpdateCheckComp extends SimpleComp { var name = Bindings.createStringBinding( () -> { if (updateReady.getValue()) { - var prefix = XPipeDistributionType.get() == XPipeDistributionType.PORTABLE ? AppI18n.get("updateReadyPortable") : AppI18n.get("updateReady"); - var version = "Version " + XPipeDistributionType.get() - .getUpdateHandler() - .getPreparedUpdate() - .getValue() - .getVersion(); + var prefix = XPipeDistributionType.get() == XPipeDistributionType.PORTABLE + ? AppI18n.get("updateReadyPortable") + : AppI18n.get("updateReady"); + var version = "Version " + + XPipeDistributionType.get() + .getUpdateHandler() + .getPreparedUpdate() + .getValue() + .getVersion(); return prefix + " (" + version + ")"; } @@ -60,7 +63,9 @@ public class UpdateCheckComp extends SimpleComp { var description = Bindings.createStringBinding( () -> { if (updateReady.getValue()) { - return XPipeDistributionType.get() == XPipeDistributionType.PORTABLE ? AppI18n.get("updateReadyDescriptionPortable") : AppI18n.get("updateReadyDescription"); + return XPipeDistributionType.get() == XPipeDistributionType.PORTABLE + ? AppI18n.get("updateReadyDescriptionPortable") + : AppI18n.get("updateReadyDescription"); } return AppI18n.get("checkForUpdatesDescription"); diff --git a/app/src/main/java/io/xpipe/app/prefs/VaultCategory.java b/app/src/main/java/io/xpipe/app/prefs/VaultCategory.java index 505cb2b40..d592baf23 100644 --- a/app/src/main/java/io/xpipe/app/prefs/VaultCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/VaultCategory.java @@ -24,25 +24,33 @@ public class VaultCategory extends AppPrefsCategory { var prefs = AppPrefs.get(); var builder = new OptionsBuilder(); if (!STORAGE_DIR_FIXED) { - var sub = new OptionsBuilder() - .nameAndDescription("storageDirectory") - .addPath(prefs.storageDirectory); - sub.withValidator(val-> { + var sub = + new OptionsBuilder().nameAndDescription("storageDirectory").addPath(prefs.storageDirectory); + sub.withValidator(val -> { sub.check(Validator.absolutePath(val, prefs.storageDirectory)); sub.check(Validator.directory(val, prefs.storageDirectory)); }); - builder.addTitle("storage") - .sub(sub); + builder.addTitle("storage").sub(sub); } - builder.addTitle("vaultSecurity").sub(new OptionsBuilder() - .nameAndDescription("encryptAllVaultData") - .addToggle(prefs.encryptAllVaultData) - .nameAndDescription("workspaceLock").addComp(new ButtonComp( - Bindings.createStringBinding(() -> { - return prefs.getLockCrypt().getValue() != null && !prefs.getLockCrypt().getValue().isEmpty() - ? AppI18n.get("changeLock") - : AppI18n.get("createLock"); - }, prefs.getLockCrypt()), LockChangeAlert::show), prefs.getLockCrypt())); + builder.addTitle("vaultSecurity") + .sub(new OptionsBuilder() + .nameAndDescription("encryptAllVaultData") + .addToggle(prefs.encryptAllVaultData) + .nameAndDescription("workspaceLock") + .addComp( + new ButtonComp( + Bindings.createStringBinding( + () -> { + return prefs.getLockCrypt().getValue() != null + && !prefs.getLockCrypt() + .getValue() + .isEmpty() + ? AppI18n.get("changeLock") + : AppI18n.get("createLock"); + }, + prefs.getLockCrypt()), + LockChangeAlert::show), + prefs.getLockCrypt())); return builder.buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/storage/ContextualFileReference.java b/app/src/main/java/io/xpipe/app/storage/ContextualFileReference.java index fd7e5207b..7e9661459 100644 --- a/app/src/main/java/io/xpipe/app/storage/ContextualFileReference.java +++ b/app/src/main/java/io/xpipe/app/storage/ContextualFileReference.java @@ -16,6 +16,8 @@ import java.util.regex.Matcher; public class ContextualFileReference { private static String lastDataDir; + @NonNull + private final String path; private static String getDataDir() { if (DataStorage.get() == null) { @@ -73,11 +75,9 @@ public class ContextualFileReference { return new ContextualFileReference(normalized(replaced)); } - @NonNull - private final String path; - public String toAbsoluteFilePath(ShellControl sc) { - return path.replaceAll("/", Matcher.quoteReplacement(sc != null ? sc.getOsType().getFileSystemSeparator() : "/")); + return path.replaceAll( + "/", Matcher.quoteReplacement(sc != null ? sc.getOsType().getFileSystemSeparator() : "/")); } public boolean isInDataDirectory() { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java index 235b003df..9eede6170 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStateProviderImpl.java @@ -15,8 +15,8 @@ public class DataStateProviderImpl extends DataStateProvider { return; } - var entry = DataStorage.get().getStoreEntryIfPresent(store) - .or(() -> DataStorage.get().getStoreEntryInProgressIfPresent(store)); + var entry = DataStorage.get().getStoreEntryIfPresent(store).or(() -> DataStorage.get() + .getStoreEntryInProgressIfPresent(store)); if (entry.isEmpty()) { return; } @@ -73,7 +73,7 @@ public class DataStateProviderImpl extends DataStateProvider { var r = entry.get().getStoreCache().get(key); if (r == null) { - r = def .get(); + r = def.get(); entry.get().setStoreCache(key, r); } return c.cast(r); diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index a7049bef9..3e31dff24 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -6,7 +6,10 @@ import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.FixedHierarchyStore; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.*; +import io.xpipe.core.store.DataStore; +import io.xpipe.core.store.DataStoreId; +import io.xpipe.core.store.FixedChildStore; +import io.xpipe.core.store.LocalStore; import io.xpipe.core.util.UuidHelper; import javafx.util.Pair; import lombok.Getter; @@ -37,29 +40,21 @@ public abstract class DataStorage { private static DataStorage INSTANCE; protected final Path dir; - - @Getter - protected boolean loaded; - @Getter protected final List storeCategories; - protected final Map storeEntries; - @Getter protected final Set storeEntriesSet; - + protected final ReentrantLock busyIo = new ReentrantLock(); + @Getter + private final List listeners = new CopyOnWriteArrayList<>(); + private final Map storeEntriesInProgress = new ConcurrentHashMap<>(); + @Getter + protected boolean loaded; @Getter @Setter protected DataStoreCategory selectedCategory; - @Getter - private final List listeners = new CopyOnWriteArrayList<>(); - - private final Map storeEntriesInProgress = new ConcurrentHashMap<>(); - - protected final ReentrantLock busyIo = new ReentrantLock(); - public DataStorage() { var prefsDir = AppPrefs.get().storageDirectory().getValue(); this.dir = !Files.exists(prefsDir) || !Files.isDirectory(prefsDir) ? AppPrefs.DEFAULT_STORAGE_DIR : prefsDir; @@ -68,20 +63,6 @@ public abstract class DataStorage { this.storeCategories = new CopyOnWriteArrayList<>(); } - public abstract String getVaultKey(); - - public DataStoreCategory getDefaultConnectionsCategory() { - return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow(); - } - - public DataStoreCategory getAllConnectionsCategory() { - return getStoreCategoryIfPresent(ALL_CONNECTIONS_CATEGORY_UUID).orElseThrow(); - } - - public DataStoreCategory getAllScriptsCategory() { - return getStoreCategoryIfPresent(ALL_SCRIPTS_CATEGORY_UUID).orElseThrow(); - } - private static boolean shouldPersist() { if (System.getProperty(PERSIST_PROP) != null) { return Boolean.parseBoolean(System.getProperty(PERSIST_PROP)); @@ -107,12 +88,6 @@ public abstract class DataStorage { }); } - public void forceRewrite() { - getStoreEntries().forEach(dataStoreEntry -> { - dataStoreEntry.reassignStore(); - }); - } - public static void reset() { if (INSTANCE == null) { return; @@ -122,6 +97,30 @@ public abstract class DataStorage { INSTANCE = null; } + public static DataStorage get() { + return INSTANCE; + } + + public abstract String getVaultKey(); + + public DataStoreCategory getDefaultConnectionsCategory() { + return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow(); + } + + public DataStoreCategory getAllConnectionsCategory() { + return getStoreCategoryIfPresent(ALL_CONNECTIONS_CATEGORY_UUID).orElseThrow(); + } + + public DataStoreCategory getAllScriptsCategory() { + return getStoreCategoryIfPresent(ALL_SCRIPTS_CATEGORY_UUID).orElseThrow(); + } + + public void forceRewrite() { + getStoreEntries().forEach(dataStoreEntry -> { + dataStoreEntry.reassignStore(); + }); + } + private synchronized void dispose() { onReset(); save(true); @@ -148,7 +147,8 @@ public abstract class DataStorage { } if (getStoreCategoryIfPresent(PREDEFINED_SCRIPTS_CATEGORY_UUID).isEmpty()) { - var cat = DataStoreCategory.createNew(ALL_SCRIPTS_CATEGORY_UUID, PREDEFINED_SCRIPTS_CATEGORY_UUID, "Predefined"); + var cat = DataStoreCategory.createNew( + ALL_SCRIPTS_CATEGORY_UUID, PREDEFINED_SCRIPTS_CATEGORY_UUID, "Predefined"); cat.setDirectory(categoriesDir.resolve(PREDEFINED_SCRIPTS_CATEGORY_UUID.toString())); storeCategories.add(cat); } @@ -160,16 +160,26 @@ public abstract class DataStorage { } if (getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).isEmpty()) { - storeCategories.add( - new DataStoreCategory(categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), DEFAULT_CATEGORY_UUID, "Default", Instant.now(), - Instant.now(), true, ALL_CONNECTIONS_CATEGORY_UUID, StoreSortMode.ALPHABETICAL_ASC, false)); + storeCategories.add(new DataStoreCategory( + categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), + DEFAULT_CATEGORY_UUID, + "Default", + Instant.now(), + Instant.now(), + true, + ALL_CONNECTIONS_CATEGORY_UUID, + StoreSortMode.ALPHABETICAL_ASC, + false)); } storeCategories.forEach(dataStoreCategory -> { - if (dataStoreCategory.getParentCategory() != null && getStoreCategoryIfPresent(dataStoreCategory.getParentCategory()).isEmpty()) { + if (dataStoreCategory.getParentCategory() != null + && getStoreCategoryIfPresent(dataStoreCategory.getParentCategory()) + .isEmpty()) { dataStoreCategory.setParentCategory(ALL_CONNECTIONS_CATEGORY_UUID); - } else if (dataStoreCategory.getParentCategory() == null && !dataStoreCategory.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID) && - !dataStoreCategory.getUuid().equals(ALL_SCRIPTS_CATEGORY_UUID)) { + } else if (dataStoreCategory.getParentCategory() == null + && !dataStoreCategory.getUuid().equals(ALL_CONNECTIONS_CATEGORY_UUID) + && !dataStoreCategory.getUuid().equals(ALL_SCRIPTS_CATEGORY_UUID)) { dataStoreCategory.setParentCategory(ALL_CONNECTIONS_CATEGORY_UUID); } }); @@ -177,10 +187,6 @@ public abstract class DataStorage { protected void onReset() {} - public static DataStorage get() { - return INSTANCE; - } - protected Path getStoresDir() { return dir.resolve("stores"); } @@ -206,7 +212,8 @@ public abstract class DataStorage { public void saveAsync() { // If we are already loading or saving, don't queue up another operation. // This could otherwise lead to thread starvation with virtual threads - // Technically the load and save operations also return instantly if locked, but let's not even create new threads here + // Technically the load and save operations also return instantly if locked, but let's not even create new + // threads here if (busyIo.isLocked()) { return; } @@ -231,8 +238,8 @@ public abstract class DataStorage { return false; } } while ((c = DataStorage.get() - .getStoreCategoryIfPresent(c.getParentCategory()) - .orElse(null)) + .getStoreCategoryIfPresent(c.getParentCategory()) + .orElse(null)) != null); return true; } @@ -307,7 +314,9 @@ public abstract class DataStorage { } public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) { - if (getStoreCategoryIfPresent(entry.getUuid()).map(category -> category.equals(newCategory)).orElse(false)) { + if (getStoreCategoryIfPresent(entry.getUuid()) + .map(category -> category.equals(newCategory)) + .orElse(false)) { return; } @@ -349,9 +358,11 @@ public abstract class DataStorage { return false; } - return newChildren.stream().filter(nc -> nc.getStore().getFixedId().isPresent()).noneMatch(nc -> { - return nc.getStore().getFixedId().getAsInt() == oid.getAsInt(); - }); + return newChildren.stream() + .filter(nc -> nc.getStore().getFixedId().isPresent()) + .noneMatch(nc -> { + return nc.getStore().getFixedId().getAsInt() == oid.getAsInt(); + }); }) .toList(); var toAdd = newChildren.stream() @@ -362,9 +373,16 @@ public abstract class DataStorage { return false; } - return oldChildren.stream().filter(oc -> ((FixedChildStore) oc.getStore()).getFixedId().isPresent()).noneMatch(oc -> { - return ((FixedChildStore) oc.getStore()).getFixedId().getAsInt() == nid.getAsInt(); - }); + return oldChildren.stream() + .filter(oc -> ((FixedChildStore) oc.getStore()) + .getFixedId() + .isPresent()) + .noneMatch(oc -> { + return ((FixedChildStore) oc.getStore()) + .getFixedId() + .getAsInt() + == nid.getAsInt(); + }); }) .toList(); var toUpdate = oldChildren.stream() @@ -394,7 +412,8 @@ public abstract class DataStorage { pair.getKey().setStoreInternal(pair.getValue().getStore(), false); // Update state by merging - if (pair.getKey().getStorePersistentState() != null && pair.getValue().get().getStorePersistentState() != null) { + if (pair.getKey().getStorePersistentState() != null + && pair.getValue().get().getStorePersistentState() != null) { var mergedState = pair.getKey().getStorePersistentState().deepCopy(); mergedState.merge(pair.getValue().get().getStorePersistentState()); pair.getKey().setStorePersistentState(mergedState); @@ -500,7 +519,8 @@ public abstract class DataStorage { public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) { for (DataStoreEntry e : es) { - if (storeEntriesSet.contains(e) || getStoreEntryIfPresent(e.getStore()).isPresent()) { + if (storeEntriesSet.contains(e) + || getStoreEntryIfPresent(e.getStore()).isPresent()) { return; } @@ -539,7 +559,11 @@ public abstract class DataStorage { return f.get(); } - var e = DataStoreEntry.createNew(UUID.randomUUID(), related != null ? related.getCategoryUuid() : selectedCategory.getUuid(), name, store); + var e = DataStoreEntry.createNew( + UUID.randomUUID(), + related != null ? related.getCategoryUuid() : selectedCategory.getUuid(), + name, + store); addStoreEntryIfNotPresent(e); return e; } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreCategory.java b/app/src/main/java/io/xpipe/app/storage/DataStoreCategory.java index ce52d0d30..40906cf00 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreCategory.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreCategory.java @@ -32,16 +32,86 @@ public class DataStoreCategory extends StorageElement { boolean share; public DataStoreCategory( - Path directory, UUID uuid, String name, Instant lastUsed, Instant lastModified, boolean dirty, - UUID parentCategory, StoreSortMode sortMode, - boolean share - ) { + Path directory, + UUID uuid, + String name, + Instant lastUsed, + Instant lastModified, + boolean dirty, + UUID parentCategory, + StoreSortMode sortMode, + boolean share) { super(directory, uuid, name, lastUsed, lastModified, dirty); this.parentCategory = parentCategory; this.sortMode = sortMode; this.share = share; } + public static DataStoreCategory createNew(UUID parentCategory, @NonNull String name) { + return new DataStoreCategory( + null, + UUID.randomUUID(), + name, + Instant.now(), + Instant.now(), + true, + parentCategory, + StoreSortMode.ALPHABETICAL_ASC, + false); + } + + public static DataStoreCategory createNew(UUID parentCategory, @NonNull UUID uuid, @NonNull String name) { + return new DataStoreCategory( + null, + uuid, + name, + Instant.now(), + Instant.now(), + true, + parentCategory, + StoreSortMode.ALPHABETICAL_ASC, + false); + } + + public static Optional fromDirectory(Path dir) throws Exception { + ObjectMapper mapper = JacksonMapper.getDefault(); + + var entryFile = dir.resolve("category.json"); + var stateFile = dir.resolve("state.json"); + if (!Files.exists(entryFile)) { + return Optional.empty(); + } + + var stateJson = + Files.exists(stateFile) ? mapper.readTree(stateFile.toFile()) : JsonNodeFactory.instance.objectNode(); + var json = mapper.readTree(entryFile.toFile()); + + var uuid = UUID.fromString(json.required("uuid").textValue()); + var parentUuid = Optional.ofNullable(json.get("parentUuid")) + .filter(jsonNode -> !jsonNode.isNull()) + .map(jsonNode -> UUID.fromString(jsonNode.textValue())) + .orElse(null); + + var name = json.required("name").textValue(); + var sortMode = Optional.ofNullable(stateJson.get("sortMode")) + .map(JsonNode::asText) + .flatMap(string -> StoreSortMode.fromId(string)) + .orElse(StoreSortMode.ALPHABETICAL_ASC); + var share = + Optional.ofNullable(json.get("share")).map(JsonNode::asBoolean).orElse(false); + var lastUsed = Optional.ofNullable(stateJson.get("lastUsed")) + .map(jsonNode -> jsonNode.textValue()) + .map(Instant::parse) + .orElse(Instant.now()); + var lastModified = Optional.ofNullable(stateJson.get("lastModified")) + .map(jsonNode -> jsonNode.textValue()) + .map(Instant::parse) + .orElse(Instant.now()); + + return Optional.of( + new DataStoreCategory(dir, uuid, name, lastUsed, lastModified, false, parentUuid, sortMode, share)); + } + public void setSortMode(StoreSortMode sortMode) { var changed = this.sortMode != sortMode; if (changed) { @@ -66,45 +136,6 @@ public class DataStoreCategory extends StorageElement { notifyUpdate(); } - public static DataStoreCategory createNew(UUID parentCategory, @NonNull String name) { - return new DataStoreCategory(null, UUID.randomUUID(), name, Instant.now(), Instant.now(), true, parentCategory, StoreSortMode.ALPHABETICAL_ASC, - false - ); - } - - public static DataStoreCategory createNew(UUID parentCategory, @NonNull UUID uuid, @NonNull String name) { - return new DataStoreCategory(null, uuid, name, Instant.now(), Instant.now(), true, parentCategory, StoreSortMode.ALPHABETICAL_ASC, false); - } - - public static Optional fromDirectory(Path dir) throws Exception { - ObjectMapper mapper = JacksonMapper.getDefault(); - - var entryFile = dir.resolve("category.json"); - var stateFile = dir.resolve("state.json"); - if (!Files.exists(entryFile)) { - return Optional.empty(); - } - - var stateJson = Files.exists(stateFile) ? mapper.readTree(stateFile.toFile()) : JsonNodeFactory.instance.objectNode(); - var json = mapper.readTree(entryFile.toFile()); - - var uuid = UUID.fromString(json.required("uuid").textValue()); - var parentUuid = Optional.ofNullable(json.get("parentUuid")).filter(jsonNode -> !jsonNode.isNull()).map(jsonNode -> UUID.fromString(jsonNode.textValue())).orElse(null); - - var name = json.required("name").textValue(); - var sortMode = Optional.ofNullable(stateJson.get("sortMode")) - .map(JsonNode::asText) - .flatMap(string -> StoreSortMode.fromId(string)) - .orElse(StoreSortMode.ALPHABETICAL_ASC); - var share = Optional.ofNullable(json.get("share")) - .map(JsonNode::asBoolean) - .orElse(false); - var lastUsed = Optional.ofNullable(stateJson.get("lastUsed")).map(jsonNode -> jsonNode.textValue()).map(Instant::parse).orElse(Instant.now()); - var lastModified = Optional.ofNullable(stateJson.get("lastModified")).map(jsonNode -> jsonNode.textValue()).map(Instant::parse).orElse(Instant.now()); - - return Optional.of(new DataStoreCategory(dir, uuid, name, lastUsed, lastModified, false, parentUuid, sortMode, share)); - } - public boolean canShare() { if (parentCategory == null) { return false; diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java b/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java index 117a44258..0f7cbb037 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreColor.java @@ -35,7 +35,6 @@ public enum DataStoreColor { public String toHexString() { var value = terminalColor; - return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue())) - .toUpperCase(); + return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue())).toUpperCase(); } } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index c94683ede..8288b48f7 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -25,36 +25,27 @@ import java.util.stream.Collectors; @Value public class DataStoreEntry extends StorageElement { + Map storeCache = new LinkedHashMap<>(); @NonFinal Validity validity; - @NonFinal @Setter JsonNode storeNode; - @Getter @NonFinal DataStore store; - @NonFinal Configuration configuration; - @NonFinal boolean expanded; - @NonFinal boolean inRefresh; - @NonFinal @Setter boolean observing; - @Getter @NonFinal DataStoreProvider provider; - - Map storeCache = new LinkedHashMap<>(); - @NonFinal UUID categoryUuid; @@ -83,8 +74,8 @@ public class DataStoreEntry extends StorageElement { Validity validity, Configuration configuration, JsonNode storePersistentState, - boolean expanded, DataStoreColor color - ) { + boolean expanded, + DataStoreColor color) { super(directory, uuid, name, lastUsed, lastModified, dirty); this.categoryUuid = categoryUuid; this.store = DataStorageParser.storeFromNode(storeNode); @@ -106,8 +97,7 @@ public class DataStoreEntry extends StorageElement { String name, Instant lastUsed, Instant lastModified, - DataStore store - ) { + DataStore store) { super(directory, uuid, name, lastUsed, lastModified, false); this.categoryUuid = categoryUuid; this.store = store; @@ -120,21 +110,6 @@ public class DataStoreEntry extends StorageElement { this.storePersistentStateNode = null; } - @Override - public boolean equals(Object o) { - return o == this || (o instanceof DataStoreEntry e && e.getUuid().equals(getUuid())); - } - - @Override - public int hashCode() { - return getUuid().hashCode(); - } - - @Override - public String toString() { - return getName(); - } - public static DataStoreEntry createTempWrapper(@NonNull DataStore store) { return new DataStoreEntry( null, @@ -143,19 +118,21 @@ public class DataStoreEntry extends StorageElement { UUID.randomUUID().toString(), Instant.now(), Instant.now(), - store - ); + store); } public static DataStoreEntry createNew(@NonNull String name, @NonNull DataStore store) { - return createNew(UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, store); + return createNew( + UUID.randomUUID(), DataStorage.get().getSelectedCategory().getUuid(), name, store); } @SneakyThrows public static DataStoreEntry createNew( @NonNull UUID uuid, @NonNull UUID categoryUuid, @NonNull String name, @NonNull DataStore store) { var node = DataStorageWriter.storeToNode(store); - var validity = DataStorageParser.storeFromNode(node) == null ? Validity.LOAD_FAILED : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE; + var validity = DataStorageParser.storeFromNode(node) == null + ? Validity.LOAD_FAILED + : store.isComplete() ? Validity.COMPLETE : Validity.INCOMPLETE; var entry = new DataStoreEntry( null, uuid, @@ -168,8 +145,8 @@ public class DataStoreEntry extends StorageElement { validity, Configuration.defaultConfiguration(), null, - false, null - ); + false, + null); return entry; } @@ -198,8 +175,8 @@ public class DataStoreEntry extends StorageElement { Validity.INCOMPLETE, configuration, storePersistentState, - expanded, color - ); + expanded, + color); } public static Optional fromDirectory(Path dir) throws Exception { @@ -262,8 +239,33 @@ public class DataStoreEntry extends StorageElement { } catch (Exception e) { ErrorEvent.fromThrowable(e).handle(); } - return Optional.of( - createExisting(dir, uuid, categoryUuid, name, lastUsed, lastModified, storeNode, configuration, persistentState, expanded, color)); + return Optional.of(createExisting( + dir, + uuid, + categoryUuid, + name, + lastUsed, + lastModified, + storeNode, + configuration, + persistentState, + expanded, + color)); + } + + @Override + public int hashCode() { + return getUuid().hashCode(); + } + + @Override + public boolean equals(Object o) { + return o == this || (o instanceof DataStoreEntry e && e.getUuid().equals(getUuid())); + } + + @Override + public String toString() { + return getName(); } public void setInRefresh(boolean newRefresh) { @@ -284,16 +286,6 @@ public class DataStoreEntry extends StorageElement { } } - public void setStorePersistentState(DataStoreState value) { - var changed = !Objects.equals(storePersistentState, value); - this.storePersistentState = value; - this.storePersistentStateNode = JacksonMapper.getDefault().valueToTree(value); - if (changed) { - this.dirty = true; - notifyUpdate(); - } - } - @SneakyThrows @SuppressWarnings("unchecked") public T getStorePersistentState() { @@ -315,6 +307,16 @@ public class DataStoreEntry extends StorageElement { return (T) sds.getStateClass().cast(storePersistentState); } + public void setStorePersistentState(DataStoreState value) { + var changed = !Objects.equals(storePersistentState, value); + this.storePersistentState = value; + this.storePersistentStateNode = JacksonMapper.getDefault().valueToTree(value); + if (changed) { + this.dirty = true; + notifyUpdate(); + } + } + public void setConfiguration(Configuration configuration) { this.configuration = configuration; this.dirty = true; @@ -332,6 +334,35 @@ public class DataStoreEntry extends StorageElement { return new Path[] {directory.resolve("store.json"), directory.resolve("entry.json")}; } + public void writeDataToDisk() throws Exception { + if (!dirty) { + return; + } + + ObjectMapper mapper = JacksonMapper.getDefault(); + ObjectNode obj = JsonNodeFactory.instance.objectNode(); + ObjectNode stateObj = JsonNodeFactory.instance.objectNode(); + obj.put("uuid", uuid.toString()); + obj.put("name", name); + obj.put("categoryUuid", categoryUuid.toString()); + stateObj.put("lastUsed", lastUsed.toString()); + stateObj.put("lastModified", lastModified.toString()); + stateObj.set("color", mapper.valueToTree(color)); + stateObj.set("persistentState", storePersistentStateNode); + obj.set("configuration", mapper.valueToTree(configuration)); + stateObj.put("expanded", expanded); + + var entryString = mapper.writeValueAsString(obj); + var stateString = mapper.writeValueAsString(stateObj); + var storeString = mapper.writeValueAsString(DataStorageEncryption.encryptNodeIfNeeded(storeNode)); + + FileUtils.forceMkdir(directory.toFile()); + Files.writeString(directory.resolve("state.json"), stateString); + Files.writeString(directory.resolve("entry.json"), entryString); + Files.writeString(directory.resolve("store.json"), storeString); + dirty = false; + } + public void setExpanded(boolean expanded) { var changed = expanded != this.expanded; this.expanded = expanded; @@ -404,7 +435,9 @@ public class DataStoreEntry extends StorageElement { if (store instanceof ValidatableStore l) { l.validate(); } else if (store instanceof FixedHierarchyStore h) { - childrenCache = h.listChildren(this).stream().map(DataStoreEntryRef::get).collect(Collectors.toSet()); + childrenCache = h.listChildren(this).stream() + .map(DataStoreEntryRef::get) + .collect(Collectors.toSet()); } } finally { setInRefresh(false); @@ -498,35 +531,6 @@ public class DataStoreEntry extends StorageElement { return getStore() != null; } - public void writeDataToDisk() throws Exception { - if (!dirty) { - return; - } - - ObjectMapper mapper = JacksonMapper.getDefault(); - ObjectNode obj = JsonNodeFactory.instance.objectNode(); - ObjectNode stateObj = JsonNodeFactory.instance.objectNode(); - obj.put("uuid", uuid.toString()); - obj.put("name", name); - obj.put("categoryUuid", categoryUuid.toString()); - stateObj.put("lastUsed", lastUsed.toString()); - stateObj.put("lastModified", lastModified.toString()); - stateObj.set("color", mapper.valueToTree(color)); - stateObj.set("persistentState", storePersistentStateNode); - obj.set("configuration", mapper.valueToTree(configuration)); - stateObj.put("expanded", expanded); - - var entryString = mapper.writeValueAsString(obj); - var stateString = mapper.writeValueAsString(stateObj); - var storeString = mapper.writeValueAsString(DataStorageEncryption.encryptNodeIfNeeded(storeNode)); - - FileUtils.forceMkdir(directory.toFile()); - Files.writeString(directory.resolve("state.json"), stateString); - Files.writeString(directory.resolve("entry.json"), entryString); - Files.writeString(directory.resolve("store.json"), storeString); - dirty = false; - } - @Getter public enum Validity { @JsonProperty("loadFailed") diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreSecret.java b/app/src/main/java/io/xpipe/app/storage/DataStoreSecret.java index f85a747ee..2039e83a3 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreSecret.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreSecret.java @@ -16,11 +16,11 @@ import java.util.Objects; @Value public class DataStoreSecret { + InPlaceSecretValue internalSecret; + String usedPasswordLockCrypt; @Setter @NonFinal TreeNode originalNode; - InPlaceSecretValue internalSecret; - String usedPasswordLockCrypt; public DataStoreSecret(InPlaceSecretValue internalSecret) { this(null, internalSecret); @@ -29,18 +29,25 @@ public class DataStoreSecret { public DataStoreSecret(TreeNode originalNode, InPlaceSecretValue internalSecret) { this.originalNode = originalNode; this.internalSecret = internalSecret; - this.usedPasswordLockCrypt = AppPrefs.get() != null ? AppPrefs.get().getLockCrypt().get() : null; + this.usedPasswordLockCrypt = + AppPrefs.get() != null ? AppPrefs.get().getLockCrypt().get() : null; } public boolean requiresRewrite() { - return AppPrefs.get() != null && AppPrefs.get().getLockCrypt().get() != null && !Objects.equals(AppPrefs.get().getLockCrypt().get(), - usedPasswordLockCrypt); + return AppPrefs.get() != null + && AppPrefs.get().getLockCrypt().get() != null + && !Objects.equals(AppPrefs.get().getLockCrypt().get(), usedPasswordLockCrypt); } public char[] getSecret() { return internalSecret != null ? internalSecret.getSecret() : new char[0]; } + @Override + public int hashCode() { + return Arrays.hashCode(getSecret()); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -52,11 +59,6 @@ public class DataStoreSecret { return Arrays.equals(getSecret(), that.getSecret()); } - @Override - public int hashCode() { - return Arrays.hashCode(getSecret()); - } - public SecretValue getOutputSecret() { if (AppPrefs.get() != null && AppPrefs.get().getLockPassword().getValue() != null) { return new PasswordLockSecretValue(getSecret()); diff --git a/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java b/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java index 1a3d6894c..51b3be27f 100644 --- a/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/ImpersistentStorage.java @@ -33,8 +33,16 @@ public class ImpersistentStorage extends DataStorage { storeCategories.add(cat); } { - var cat = new DataStoreCategory(categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), DEFAULT_CATEGORY_UUID, "Default", Instant.now(), - Instant.now(), true, ALL_CONNECTIONS_CATEGORY_UUID, StoreSortMode.ALPHABETICAL_ASC, true); + var cat = new DataStoreCategory( + categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), + DEFAULT_CATEGORY_UUID, + "Default", + Instant.now(), + Instant.now(), + true, + ALL_CONNECTIONS_CATEGORY_UUID, + StoreSortMode.ALPHABETICAL_ASC, + true); storeCategories.add(cat); selectedCategory = getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow(); } @@ -48,11 +56,6 @@ public class ImpersistentStorage extends DataStorage { e.validate(); } - @Override - public boolean supportsSharing() { - return false; - } - @Override public synchronized void save(boolean dispose) { var storesDir = getStoresDir(); @@ -67,4 +70,8 @@ public class ImpersistentStorage extends DataStorage { } } + @Override + public boolean supportsSharing() { + return false; + } } diff --git a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java index 8879d839d..ffe67b39d 100644 --- a/app/src/main/java/io/xpipe/app/storage/StandardStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/StandardStorage.java @@ -22,6 +22,7 @@ import java.util.stream.Stream; public class StandardStorage extends DataStorage { private final List directoriesToKeep = new ArrayList<>(); + @Getter private final GitStorageHandler gitStorageHandler; @@ -40,106 +41,6 @@ public class StandardStorage extends DataStorage { return vaultKey; } - private void deleteLeftovers() { - var storesDir = getStoresDir(); - var categoriesDir = getCategoriesDir(); - - // Delete leftover directories in entries dir - try (var s = Files.list(storesDir)) { - s.forEach(file -> { - if (directoriesToKeep.contains(file)) { - return; - } - - var name = file.getFileName().toString(); - try { - UUID uuid; - try { - uuid = UUID.fromString(name); - } catch (Exception ex) { - FileUtils.forceDelete(file.toFile()); - return; - } - - var entry = getStoreEntryIfPresent(uuid); - if (entry.isEmpty()) { - TrackEvent.withTrace("Deleting leftover store directory") - .tag("uuid", uuid) - .handle(); - FileUtils.forceDelete(file.toFile()); - gitStorageHandler.handleDeletion(file,uuid.toString()); - } - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); - } - }); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); - } - - // Delete leftover directories in categories dir - try (var s = Files.list(categoriesDir)) { - s.forEach(file -> { - if (directoriesToKeep.contains(file)) { - return; - } - - var name = file.getFileName().toString(); - try { - UUID uuid; - try { - uuid = UUID.fromString(name); - } catch (Exception ex) { - FileUtils.forceDelete(file.toFile()); - return; - } - - var entry = getStoreCategoryIfPresent(uuid); - if (entry.isEmpty()) { - TrackEvent.withTrace("Deleting leftover category directory") - .tag("uuid", uuid) - .handle(); - FileUtils.forceDelete(file.toFile()); - gitStorageHandler.handleDeletion(file,uuid.toString()); - } - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); - } - }); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); - } - } - - private void initVaultKey() throws IOException { - var file = dir.resolve("vaultkey"); - if (Files.exists(file)) { - var s = Files.readString(file); - vaultKey = new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8); - } else { - Files.createDirectories(dir); - vaultKey = UUID.randomUUID().toString(); - Files.writeString(file,Base64.getEncoder().encodeToString(vaultKey.getBytes(StandardCharsets.UTF_8))); - } - } - - private void initSystemInfo() throws IOException { - var file = dir.resolve("systeminfo"); - if (Files.exists(file)) { - var s = Files.readString(file); - if (!LocalShell.getShell().getOsName().equals(s)) { - ErrorEvent.fromMessage("This vault was originally created on a different system running " + s + - ". Sharing connection information between systems directly might cause some problems." + - " If you want to properly synchronize connection information across many systems, you can take a look into the git vault synchronization functionality in the settings." - ).expected().handle(); - } - } else { - Files.createDirectories(dir); - var s = LocalShell.getShell().getOsName(); - Files.writeString(file, s); - } - } - public void load() { if (!busyIo.tryLock()) { return; @@ -188,7 +89,7 @@ public class StandardStorage extends DataStorage { // IO exceptions are not expected exception.set(new IOException("Unable to load data from " + path + ". Is it corrupted?", ex)); directoriesToKeep.add(path); - } catch (Exception ex) { + } catch (Exception ex) { // Data corruption and schema changes are expected ErrorEvent.fromThrowable(ex).expected().omit().build().handle(); } @@ -227,7 +128,7 @@ public class StandardStorage extends DataStorage { // IO exceptions are not expected exception.set(new IOException("Unable to load data from " + path + ". Is it corrupted?", ex)); directoriesToKeep.add(path); - } catch (Exception ex) { + } catch (Exception ex) { // Data corruption and schema changes are expected // We only keep invalid entries in developer mode as there's no point in keeping them in @@ -248,7 +149,7 @@ public class StandardStorage extends DataStorage { storeEntriesSet.forEach(dataStoreCategory -> { if (dataStoreCategory.getCategoryUuid() == null || getStoreCategoryIfPresent(dataStoreCategory.getCategoryUuid()) - .isEmpty()) { + .isEmpty()) { dataStoreCategory.setCategoryUuid(DEFAULT_CATEGORY_UUID); } }); @@ -257,7 +158,8 @@ public class StandardStorage extends DataStorage { ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); } - var hasFixedLocal = storeEntriesSet.stream().anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID)); + var hasFixedLocal = storeEntriesSet.stream() + .anyMatch(dataStoreEntry -> dataStoreEntry.getUuid().equals(LOCAL_ID)); if (hasFixedLocal) { var local = getStoreEntry(LOCAL_ID); @@ -298,11 +200,14 @@ public class StandardStorage extends DataStorage { // Save to apply changes if (!hasFixedLocal) { - storeEntriesSet.removeIf(dataStoreEntry -> !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore); - storeEntriesSet.stream().filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).forEach(entry -> { - entry.dirty = true; - entry.setStoreNode(DataStorageWriter.storeToNode(entry.getStore())); - }); + storeEntriesSet.removeIf(dataStoreEntry -> + !dataStoreEntry.getUuid().equals(LOCAL_ID) && dataStoreEntry.getStore() instanceof LocalStore); + storeEntriesSet.stream() + .filter(entry -> entry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED) + .forEach(entry -> { + entry.dirty = true; + entry.setStoreNode(DataStorageWriter.storeToNode(entry.getStore())); + }); save(false); } @@ -347,7 +252,7 @@ public class StandardStorage extends DataStorage { } catch (IOException ex) { // IO exceptions are not expected exception.set(ex); - } catch (Exception ex) { + } catch (Exception ex) { // Data corruption and schema changes are expected ErrorEvent.fromThrowable(ex).expected().omit().build().handle(); } @@ -385,4 +290,106 @@ public class StandardStorage extends DataStorage { public boolean supportsSharing() { return gitStorageHandler.supportsShare(); } + + private void deleteLeftovers() { + var storesDir = getStoresDir(); + var categoriesDir = getCategoriesDir(); + + // Delete leftover directories in entries dir + try (var s = Files.list(storesDir)) { + s.forEach(file -> { + if (directoriesToKeep.contains(file)) { + return; + } + + var name = file.getFileName().toString(); + try { + UUID uuid; + try { + uuid = UUID.fromString(name); + } catch (Exception ex) { + FileUtils.forceDelete(file.toFile()); + return; + } + + var entry = getStoreEntryIfPresent(uuid); + if (entry.isEmpty()) { + TrackEvent.withTrace("Deleting leftover store directory") + .tag("uuid", uuid) + .handle(); + FileUtils.forceDelete(file.toFile()); + gitStorageHandler.handleDeletion(file, uuid.toString()); + } + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); + } + }); + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); + } + + // Delete leftover directories in categories dir + try (var s = Files.list(categoriesDir)) { + s.forEach(file -> { + if (directoriesToKeep.contains(file)) { + return; + } + + var name = file.getFileName().toString(); + try { + UUID uuid; + try { + uuid = UUID.fromString(name); + } catch (Exception ex) { + FileUtils.forceDelete(file.toFile()); + return; + } + + var entry = getStoreCategoryIfPresent(uuid); + if (entry.isEmpty()) { + TrackEvent.withTrace("Deleting leftover category directory") + .tag("uuid", uuid) + .handle(); + FileUtils.forceDelete(file.toFile()); + gitStorageHandler.handleDeletion(file, uuid.toString()); + } + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).omitted(true).build().handle(); + } + }); + } catch (Exception ex) { + ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); + } + } + + private void initVaultKey() throws IOException { + var file = dir.resolve("vaultkey"); + if (Files.exists(file)) { + var s = Files.readString(file); + vaultKey = new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8); + } else { + Files.createDirectories(dir); + vaultKey = UUID.randomUUID().toString(); + Files.writeString(file, Base64.getEncoder().encodeToString(vaultKey.getBytes(StandardCharsets.UTF_8))); + } + } + + private void initSystemInfo() throws IOException { + var file = dir.resolve("systeminfo"); + if (Files.exists(file)) { + var s = Files.readString(file); + if (!LocalShell.getShell().getOsName().equals(s)) { + ErrorEvent.fromMessage( + "This vault was originally created on a different system running " + s + + ". Sharing connection information between systems directly might cause some problems." + + " If you want to properly synchronize connection information across many systems, you can take a look into the git vault synchronization functionality in the settings.") + .expected() + .handle(); + } + } else { + Files.createDirectories(dir); + var s = LocalShell.getShell().getOsName(); + Files.writeString(file, s); + } + } } diff --git a/app/src/main/java/io/xpipe/app/storage/StorageElement.java b/app/src/main/java/io/xpipe/app/storage/StorageElement.java index 42a080044..8582a39b8 100644 --- a/app/src/main/java/io/xpipe/app/storage/StorageElement.java +++ b/app/src/main/java/io/xpipe/app/storage/StorageElement.java @@ -17,9 +17,12 @@ public abstract class StorageElement { @Getter protected final UUID uuid; + protected final List listeners = new ArrayList<>(); + @Getter protected boolean dirty; + @Getter protected Path directory; @@ -28,6 +31,7 @@ public abstract class StorageElement { @Getter protected Instant lastUsed; + @Getter protected Instant lastModified; diff --git a/app/src/main/java/io/xpipe/app/storage/StorageJacksonModule.java b/app/src/main/java/io/xpipe/app/storage/StorageJacksonModule.java index 390ed0859..537b8de5f 100644 --- a/app/src/main/java/io/xpipe/app/storage/StorageJacksonModule.java +++ b/app/src/main/java/io/xpipe/app/storage/StorageJacksonModule.java @@ -37,7 +37,8 @@ public class StorageJacksonModule extends SimpleModule { public static class LocalFileReferenceSerializer extends JsonSerializer { @Override - public void serialize(ContextualFileReference value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(ContextualFileReference value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { jgen.writeString(value.serialize()); } } @@ -53,7 +54,8 @@ public class StorageJacksonModule extends SimpleModule { public static class DataStoreSecretSerializer extends JsonSerializer { @Override - public void serialize(DataStoreSecret value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(DataStoreSecret value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { // Preserve same output if not changed if (value.getOriginalNode() != null && !value.requiresRewrite()) { var tree = JsonNodeFactory.instance.objectNode(); @@ -106,7 +108,8 @@ public class StorageJacksonModule extends SimpleModule { public static class DataStoreEntryRefSerializer extends JsonSerializer { @Override - public void serialize(DataStoreEntryRef value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(DataStoreEntryRef value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { if (value == null) { jgen.writeNull(); return; @@ -134,7 +137,10 @@ public class StorageJacksonModule extends SimpleModule { } var id = UUID.fromString(text); - var e = DataStorage.get().getStoreEntryIfPresent(id).filter(dataStoreEntry -> dataStoreEntry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED).orElse(null); + var e = DataStorage.get() + .getStoreEntryIfPresent(id) + .filter(dataStoreEntry -> dataStoreEntry.getValidity() != DataStoreEntry.Validity.LOAD_FAILED) + .orElse(null); if (e == null) { return null; } diff --git a/app/src/main/java/io/xpipe/app/storage/StorageListener.java b/app/src/main/java/io/xpipe/app/storage/StorageListener.java index f01825cb0..730a26884 100644 --- a/app/src/main/java/io/xpipe/app/storage/StorageListener.java +++ b/app/src/main/java/io/xpipe/app/storage/StorageListener.java @@ -6,7 +6,6 @@ public interface StorageListener { void onStoreRemove(DataStoreEntry... entry); - void onCategoryAdd(DataStoreCategory category); void onCategoryRemove(DataStoreCategory category); diff --git a/app/src/main/java/io/xpipe/app/test/ExtensionTest.java b/app/src/main/java/io/xpipe/app/test/ExtensionTest.java index ba38e695f..42ca5cb4f 100644 --- a/app/src/main/java/io/xpipe/app/test/ExtensionTest.java +++ b/app/src/main/java/io/xpipe/app/test/ExtensionTest.java @@ -12,7 +12,11 @@ public class ExtensionTest { public static Path getResourcePath(Class c, String name) { var loc = Path.of(c.getProtectionDomain().getCodeSource().getLocation().toURI()); var testName = FileNames.getBaseName(loc.getFileName().toString()).split("-")[1]; - var f = loc.getParent().getParent().resolve("resources").resolve(testName).resolve(name); + var f = loc.getParent() + .getParent() + .resolve("resources") + .resolve(testName) + .resolve(name); if (!Files.exists(f)) { throw new IllegalArgumentException(String.format("File %s does not exist", name)); } diff --git a/app/src/main/java/io/xpipe/app/update/AppDownloads.java b/app/src/main/java/io/xpipe/app/update/AppDownloads.java index f42912221..f7b5f3cf8 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDownloads.java +++ b/app/src/main/java/io/xpipe/app/update/AppDownloads.java @@ -80,8 +80,10 @@ public class AppDownloads { } try { - var url = URI.create("https://api.xpipe.io/changelog?from=" + AppProperties.get().getVersion() + - "&to=" + version + "&stage=" + AppProperties.get().isStaging()).toURL(); + var url = URI.create("https://api.xpipe.io/changelog?from=" + + AppProperties.get().getVersion() + "&to=" + version + "&stage=" + + AppProperties.get().isStaging()) + .toURL(); var bytes = HttpHelper.executeGet(url, aFloat -> {}); var string = new String(bytes, StandardCharsets.UTF_8); var json = JacksonMapper.getDefault().readTree(string); @@ -123,7 +125,8 @@ public class AppDownloads { var preIncluding = getLatestIncludingPreRelease(); // If we are currently running a prerelease, always return this as the suitable release! - if (preIncluding.isPresent() && preIncluding.get().isPrerelease() + if (preIncluding.isPresent() + && preIncluding.get().isPrerelease() && AppProperties.get().getVersion().equals(preIncluding.get().getTagName())) { return preIncluding; } diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index b442fadaf..478a5b6e2 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -61,15 +61,11 @@ public class AppInstaller { @JsonTypeName("msi") public static final class Msi extends InstallerAssetType { - @Override - public String getExtension() { - return ".msi"; - } - @Override public void installLocal(String file) throws Exception { var shellProcessControl = new LocalStore().control().start(); - var exec = XPipeInstallation.getCurrentInstallationBasePath().resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal())); + var exec = XPipeInstallation.getCurrentInstallationBasePath() + .resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal())); var logsDir = FileNames.join(XPipeInstallation.getDataDir().toString(), "logs"); var logFile = FileNames.join(logsDir, "installer_" + FileNames.getFileName(file) + ".log"); var script = ScriptHelper.createExecScript( @@ -83,16 +79,16 @@ public class AppInstaller { file, logFile, exec)); shellProcessControl.executeSimpleCommand("start \"\" /min \"" + script + "\""); } + + @Override + public String getExtension() { + return ".msi"; + } } @JsonTypeName("debian") public static final class Debian extends InstallerAssetType { - @Override - public String getExtension() { - return ".deb"; - } - @Override public void installLocal(String file) throws Exception { var name = AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe"; @@ -113,15 +109,15 @@ public class AppInstaller { file, file, name)); TerminalLauncher.open("XPipe Updater", command); } + + @Override + public String getExtension() { + return ".deb"; + } } @JsonTypeName("rpm") public static final class Rpm extends InstallerAssetType { - @Override - public String getExtension() { - return ".rpm"; - } - @Override public void installLocal(String file) throws Exception { var name = AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe"; @@ -142,15 +138,15 @@ public class AppInstaller { file, file, name)); TerminalLauncher.open("XPipe Updater", command); } + + @Override + public String getExtension() { + return ".rpm"; + } } @JsonTypeName("pkg") public static final class Pkg extends InstallerAssetType { - @Override - public String getExtension() { - return ".pkg"; - } - @Override public void installLocal(String file) throws Exception { var name = AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe"; @@ -170,6 +166,11 @@ public class AppInstaller { file, file, name)); TerminalLauncher.open("XPipe Updater", command); } + + @Override + public String getExtension() { + return ".pkg"; + } } } } diff --git a/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java b/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java index f0b836f3d..dcc83c3e6 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java @@ -22,8 +22,8 @@ public class UpdateAvailableAlert { var update = AppWindowHelper.showBlockingAlert(alert -> { alert.setTitle(AppI18n.get("updateReadyAlertTitle")); alert.setAlertType(Alert.AlertType.NONE); - var markdown = new MarkdownComp(u.getBody() != null ? u.getBody() : "", s -> " " + s) - .createRegion(); + var markdown = + new MarkdownComp(u.getBody() != null ? u.getBody() : "", s -> " " + s).createRegion(); alert.getButtonTypes().clear(); var updaterContent = uh.createInterface(); if (updaterContent != null) { diff --git a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java b/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java index 995aba0c9..f33fcbf3e 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateChangelogAlert.java @@ -17,13 +17,15 @@ public class UpdateChangelogAlert { public static void showIfNeeded() { var update = XPipeDistributionType.get().getUpdateHandler().getPerformedUpdate(); if (update != null && !XPipeDistributionType.get().getUpdateHandler().isUpdateSucceeded()) { - ErrorEvent.fromMessage(""" + ErrorEvent.fromMessage( + """ Update installation did not succeed. - + Note that you can also install the latest version manually from %s if there are any problems with the automatic update installation. - """.formatted(Hyperlinks.GITHUB + "/releases/latest") - ).handle(); + """ + .formatted(Hyperlinks.GITHUB + "/releases/latest")) + .handle(); return; } @@ -42,11 +44,11 @@ public class UpdateChangelogAlert { alert.setAlertType(Alert.AlertType.NONE); alert.initModality(Modality.NONE); - var markdown = new MarkdownComp(update.getRawDescription(), s -> " " + s) - .createRegion(); + var markdown = new MarkdownComp(update.getRawDescription(), s -> " " + s).createRegion(); alert.getDialogPane().setContent(markdown); alert.getButtonTypes().add(new ButtonType(AppI18n.get("gotIt"), ButtonBar.ButtonData.OK_DONE)); - }, r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {})); + }, + r -> r.filter(b -> b.getButtonData().isDefaultButton()).ifPresent(t -> {})); } } diff --git a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java index 6d7fcf0cb..c8cba8b65 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java @@ -211,7 +211,8 @@ public abstract class UpdateHandler { return; } - if (available != null && !available.getVersion().equals(preparedUpdate.getValue().getVersion())) { + if (available != null + && !available.getVersion().equals(preparedUpdate.getValue().getVersion())) { preparedUpdate.setValue(null); return; } diff --git a/app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java b/app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java index 2d081a2b6..593d49e5b 100644 --- a/app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java +++ b/app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java @@ -23,6 +23,12 @@ public enum XPipeDistributionType { CHOCO("choco", true, () -> new ChocoUpdater()); private static XPipeDistributionType type; + @Getter + private final String id; + @Getter + private final boolean supportsUrls; + private final Supplier updateHandlerSupplier; + private UpdateHandler updateHandler; XPipeDistributionType(String id, boolean supportsUrls, Supplier updateHandlerSupplier) { this.id = id; @@ -62,7 +68,9 @@ public enum XPipeDistributionType { type = det; AppCache.update("dist", type.getId()); - TrackEvent.withInfo("Determined distribution type").tag("type",type.getId()).handle(); + TrackEvent.withInfo("Determined distribution type") + .tag("type", type.getId()) + .handle(); } public static XPipeDistributionType get() { @@ -84,7 +92,8 @@ public enum XPipeDistributionType { } try (var sc = LocalShell.getShell()) { - // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the production behavior + // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the + // production behavior if (OsType.getLocal().equals(OsType.WINDOWS)) { try (var chocoOut = sc.command("choco search --local-only -r xpipe").start()) { @@ -101,7 +110,8 @@ public enum XPipeDistributionType { } } - // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the production behavior + // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the + // production behavior if (OsType.getLocal().equals(OsType.MACOS)) { try (var brewOut = sc.command("brew list --casks --versions").start()) { var out = brewOut.readStdoutDiscardErr(); @@ -124,14 +134,6 @@ public enum XPipeDistributionType { return XPipeDistributionType.NATIVE_INSTALLATION; } - @Getter - private final String id; - @Getter - private final boolean supportsUrls; - - private UpdateHandler updateHandler; - private final Supplier updateHandlerSupplier; - public UpdateHandler getUpdateHandler() { if (updateHandler == null) { updateHandler = updateHandlerSupplier.get(); diff --git a/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java b/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java index fa9ac0c09..f787d43f2 100644 --- a/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java +++ b/app/src/main/java/io/xpipe/app/util/ApplicationHelper.java @@ -70,8 +70,8 @@ public class ApplicationHelper { ShellControl processControl, String executable, String displayName, DataStoreEntry connection) throws Exception { if (!isInPath(processControl, executable)) { - throw ErrorEvent.unreportable(new IOException(displayName + " executable " + executable + " not found in PATH" - + (connection != null ? " on system " + connection.getName() : ""))); + throw ErrorEvent.unreportable(new IOException(displayName + " executable " + executable + + " not found in PATH" + (connection != null ? " on system " + connection.getName() : ""))); } } @@ -79,7 +79,7 @@ public class ApplicationHelper { throws Exception { if (!supplier.get()) { throw ErrorEvent.unreportable(new IOException(displayName + " is not supported" - + (connection != null ? " on system " + connection.getName() : ""))); + + (connection != null ? " on system " + connection.getName() : ""))); } } } diff --git a/app/src/main/java/io/xpipe/app/util/AskpassAlert.java b/app/src/main/java/io/xpipe/app/util/AskpassAlert.java index 3e8cff183..ce6b3ca62 100644 --- a/app/src/main/java/io/xpipe/app/util/AskpassAlert.java +++ b/app/src/main/java/io/xpipe/app/util/AskpassAlert.java @@ -60,13 +60,12 @@ public class AskpassAlert { alert.setOnHiding(event -> { anim.stop(); }); - }) .filter(b -> b.getButtonData().isDefaultButton()) .map(t -> { return prop.getValue() != null ? prop.getValue() : InPlaceSecretValue.of(""); }) .orElse(null); - return new SecretQueryResult(r,r == null); + return new SecretQueryResult(r, r == null); } } diff --git a/app/src/main/java/io/xpipe/app/util/BooleanScope.java b/app/src/main/java/io/xpipe/app/util/BooleanScope.java index 02ee23614..08f49d187 100644 --- a/app/src/main/java/io/xpipe/app/util/BooleanScope.java +++ b/app/src/main/java/io/xpipe/app/util/BooleanScope.java @@ -6,6 +6,14 @@ import javafx.beans.property.BooleanProperty; public class BooleanScope implements AutoCloseable { + private final BooleanProperty prop; + private boolean invert; + private boolean forcePlatform; + private boolean wait; + public BooleanScope(BooleanProperty prop) { + this.prop = prop; + } + public static void execute(BooleanProperty prop, FailableRunnable r) throws E { try (var ignored = new BooleanScope(prop).start()) { r.run(); @@ -18,11 +26,6 @@ public class BooleanScope implements AutoCloseable { } } - private final BooleanProperty prop; - private boolean invert; - private boolean forcePlatform; - private boolean wait; - public BooleanScope exclusive() { this.wait = true; return this; @@ -38,10 +41,6 @@ public class BooleanScope implements AutoCloseable { return this; } - public BooleanScope(BooleanProperty prop) { - this.prop = prop; - } - public BooleanScope start() { if (wait) { while (!invert == prop.get()) { diff --git a/app/src/main/java/io/xpipe/app/util/DataStoreCategoryChoiceComp.java b/app/src/main/java/io/xpipe/app/util/DataStoreCategoryChoiceComp.java index 82fd90cba..f274250d7 100644 --- a/app/src/main/java/io/xpipe/app/util/DataStoreCategoryChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/util/DataStoreCategoryChoiceComp.java @@ -19,7 +19,8 @@ public class DataStoreCategoryChoiceComp extends SimpleComp { private final Property external; private final Property value; - public DataStoreCategoryChoiceComp(StoreCategoryWrapper root, Property external, Property value) { + public DataStoreCategoryChoiceComp( + StoreCategoryWrapper root, Property external, Property value) { this.root = root; this.external = external; this.value = value; @@ -53,7 +54,6 @@ public class DataStoreCategoryChoiceComp extends SimpleComp { return box; } - @EqualsAndHashCode(callSuper = true) @Value private static class Cell extends ListCell { diff --git a/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java b/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java index 2ae948012..eb3ef9946 100644 --- a/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java +++ b/app/src/main/java/io/xpipe/app/util/DataStoreFormatter.java @@ -32,8 +32,7 @@ public class DataStoreFormatter { return null; } - return name.substring(0, 1).toUpperCase() - + name.substring(1).toLowerCase(); + return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase(); } public static String formatSubHost(IntFunction func, DataStore at, int length) { @@ -61,9 +60,8 @@ public class DataStoreFormatter { } public static String formatViaProxy(IntFunction func, DataStoreEntry at, int length) { - var atString = at.getStore() instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) - ? at.getName() - : null; + var atString = + at.getStore() instanceof ShellStore shellStore && !ShellStore.isLocal(shellStore) ? at.getName() : null; if (atString == null) { return func.apply(length); } @@ -85,7 +83,7 @@ public class DataStoreFormatter { return "?"; } - return cut(input.getName(), length); + return cut(input.getName(), length); } public static String split(String left, String separator, String right, int length) { diff --git a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java index 638cc212c..5beb63dfe 100644 --- a/app/src/main/java/io/xpipe/app/util/DesktopHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DesktopHelper.java @@ -11,7 +11,8 @@ public class DesktopHelper { public static Path getDesktopDirectory() throws Exception { if (OsType.getLocal() == OsType.WINDOWS) { - return Path.of(LocalShell.getLocalPowershell().executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)")); + return Path.of(LocalShell.getLocalPowershell() + .executeSimpleStringCommand("[Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)")); } else if (OsType.getLocal() == OsType.LINUX) { try (var cmd = LocalShell.getShell().command("xdg-user-dir DESKTOP").start()) { var read = cmd.readStdoutDiscardErr(); diff --git a/app/src/main/java/io/xpipe/app/util/DialogHelper.java b/app/src/main/java/io/xpipe/app/util/DialogHelper.java index 8acbe27b9..9ca512c18 100644 --- a/app/src/main/java/io/xpipe/app/util/DialogHelper.java +++ b/app/src/main/java/io/xpipe/app/util/DialogHelper.java @@ -5,11 +5,7 @@ import io.xpipe.core.charsetter.NewLine; import io.xpipe.core.charsetter.StreamCharset; import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.QueryConverter; -import io.xpipe.core.store.LocalStore; -import io.xpipe.core.store.DataFlow; -import io.xpipe.core.store.DataStore; -import io.xpipe.core.store.FileSystem; -import io.xpipe.core.store.ShellStore; +import io.xpipe.core.store.*; import io.xpipe.core.util.SecretValue; import lombok.Value; diff --git a/app/src/main/java/io/xpipe/app/util/DiscreteProgressScope.java b/app/src/main/java/io/xpipe/app/util/DiscreteProgressScope.java index 17ea49611..09c0e9f0c 100644 --- a/app/src/main/java/io/xpipe/app/util/DiscreteProgressScope.java +++ b/app/src/main/java/io/xpipe/app/util/DiscreteProgressScope.java @@ -4,8 +4,8 @@ import javafx.beans.property.DoubleProperty; public class DiscreteProgressScope extends ProgressScope { - private int counter = 0; private final int steps; + private int counter = 0; public DiscreteProgressScope(DoubleProperty prop, int steps) { super(prop); diff --git a/app/src/main/java/io/xpipe/app/util/FileBridge.java b/app/src/main/java/io/xpipe/app/util/FileBridge.java index 757165371..66787ff64 100644 --- a/app/src/main/java/io/xpipe/app/util/FileBridge.java +++ b/app/src/main/java/io/xpipe/app/util/FileBridge.java @@ -56,7 +56,7 @@ public class FileBridge { } AppFileWatcher.getInstance().startWatchersInDirectories(List.of(TEMP), (changed, kind) -> { - INSTANCE.handleWatchEvent(changed,kind); + INSTANCE.handleWatchEvent(changed, kind); }); } catch (IOException e) { ErrorEvent.fromThrowable(e).handle(); @@ -79,8 +79,7 @@ public class FileBridge { // Wait for edit to finish in case external editor has write lock if (!Files.exists(changed)) { event("File " + TEMP.relativize(e.file) + " is probably still writing ..."); - ThreadHelper.sleep( - AppPrefs.get().editorReloadTimeout().getValue()); + ThreadHelper.sleep(AppPrefs.get().editorReloadTimeout().getValue()); // If still no read lock after 500ms, just don't parse it if (!Files.exists(changed)) { @@ -91,7 +90,8 @@ public class FileBridge { try { event("Registering modification for file " + TEMP.relativize(e.file)); - event("Last modification for file: " + e.lastModified.toString() + " vs current one: " + e.getLastModified()); + event("Last modification for file: " + e.lastModified.toString() + " vs current one: " + + e.getLastModified()); if (e.hasChanged()) { event("Registering change for file " + TEMP.relativize(e.file) + " for editor entry " + e.getName()); e.registerChange(); @@ -173,7 +173,8 @@ public class FileBridge { if (ext.isPresent()) { var existingFile = ext.get().file; try { - try (var out = Files.newOutputStream(existingFile); var in = input.get()) { + try (var out = Files.newOutputStream(existingFile); + var in = input.get()) { in.transferTo(out); } } catch (Exception ex) { @@ -185,7 +186,8 @@ public class FileBridge { return; } - Path file = TEMP.resolve(UUID.randomUUID().toString().substring(0, 6)).resolve(getFileSystemCompatibleName(keyName)); + Path file = TEMP.resolve(UUID.randomUUID().toString().substring(0, 6)) + .resolve(getFileSystemCompatibleName(keyName)); try { FileUtils.forceMkdirParent(file.toFile()); try (var out = Files.newOutputStream(file); diff --git a/app/src/main/java/io/xpipe/app/util/FileOpener.java b/app/src/main/java/io/xpipe/app/util/FileOpener.java index b2ee568c3..bc6deaf20 100644 --- a/app/src/main/java/io/xpipe/app/util/FileOpener.java +++ b/app/src/main/java/io/xpipe/app/util/FileOpener.java @@ -71,9 +71,7 @@ public class FileOpener { try { editor.launch(Path.of(localFile).toRealPath()); } catch (Exception e) { - ErrorEvent.fromThrowable(e) - .expected() - .handle(); + ErrorEvent.fromThrowable(e).expected().handle(); } } @@ -85,8 +83,9 @@ public class FileOpener { LocalShell.getShell().executeSimpleCommand(cmd); } case OsType.Linux linux -> { - LocalShell.getShell().executeSimpleCommand("mimeopen -a " - + LocalShell.getShell().getShellDialect().fileArgument(localFile)); + LocalShell.getShell() + .executeSimpleCommand("mimeopen -a " + + LocalShell.getShell().getShellDialect().fileArgument(localFile)); } case OsType.MacOs macOs -> { throw new UnsupportedOperationException(); diff --git a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java index 98456973c..2ed387ac3 100644 --- a/app/src/main/java/io/xpipe/app/util/Hyperlinks.java +++ b/app/src/main/java/io/xpipe/app/util/Hyperlinks.java @@ -9,9 +9,12 @@ public class Hyperlinks { public static final String EULA = "https://docs.xpipe.io/end-user-license-agreement"; public static final String SECURITY = "https://docs.xpipe.io/security"; public static final String DISCORD = "https://discord.gg/8y89vS8cRb"; - public static final String SLACK = "https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg"; + public static final String SLACK = + "https://join.slack.com/t/XPipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg"; - static final String[] browsers = {"xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open"}; + static final String[] browsers = { + "xdg-open", "google-chrome", "firefox", "opera", "konqueror", "mozilla", "gnome-open", "open" + }; @SuppressWarnings("deprecation") public static void open(String uri) { diff --git a/app/src/main/java/io/xpipe/app/util/Indicator.java b/app/src/main/java/io/xpipe/app/util/Indicator.java index 657574597..03aac1296 100644 --- a/app/src/main/java/io/xpipe/app/util/Indicator.java +++ b/app/src/main/java/io/xpipe/app/util/Indicator.java @@ -12,31 +12,31 @@ public class Indicator { private static final Color rColor = Color.rgb(0x0f, 0x87, 0xc3); private static final PathElement[] ELEMS = new PathElement[] { - new MoveTo(9.2362945,19.934046), - new CubicCurveTo(-1.3360939,-0.28065, -1.9963146,-1.69366, -1.9796182,-2.95487), - new CubicCurveTo(-0.1152909,-1.41268, -0.5046634,-3.07081, -1.920768,-3.72287), - new CubicCurveTo(-1.4711631,-0.77284, -3.4574873,-0.11153, -4.69154031,-1.40244), - new CubicCurveTo(-1.30616123,-1.40422, -0.5308003,-4.1855799, 1.46313121,-4.4219799), - new CubicCurveTo(1.4290018,-0.25469, 3.1669517,-0.0875, 4.1676818,-1.36207), - new CubicCurveTo(0.9172241,-1.12206, 0.9594176,-2.63766, 1.0685793,-4.01259), - new CubicCurveTo(0.4020299,-1.95732999, 3.2823027,-2.72818999, 4.5638567,-1.15760999), - new CubicCurveTo(1.215789,1.31824999, 0.738899,3.90740999, -1.103778,4.37267999), - new CubicCurveTo(-1.3972543,0.40868, -3.0929979,0.0413, -4.2208253,1.16215), - new CubicCurveTo(-1.3524806,1.26423, -1.3178578,3.29187, -1.1086673,4.9895199), - new CubicCurveTo(0.167826,1.28946, 1.0091133,2.5347, 2.3196964,2.86608), - new CubicCurveTo(1.6253079,0.53477, 3.4876372,0.45004, 5.0294052,-0.30121), - new CubicCurveTo(1.335829,-0.81654, 1.666839,-2.49408, 1.717756,-3.9432), - new CubicCurveTo(0.08759,-1.1232899, 0.704887,-2.3061299, 1.871843,-2.5951699), - new CubicCurveTo(1.534558,-0.50726, 3.390804,0.62784, 3.467269,2.28631), - new CubicCurveTo(0.183147,1.4285099, -0.949563,2.9179999, -2.431156,2.9383699), - new CubicCurveTo(-1.390597,0.17337, -3.074035,0.18128, -3.971365,1.45069), - new CubicCurveTo(-0.99314,1.271, -0.676157,2.98683, -1.1715,4.43018), - new CubicCurveTo(-0.518248,1.11436, -1.909118,1.63902, -3.0700005,1.37803), - new ClosePath() + new MoveTo(9.2362945, 19.934046), + new CubicCurveTo(-1.3360939, -0.28065, -1.9963146, -1.69366, -1.9796182, -2.95487), + new CubicCurveTo(-0.1152909, -1.41268, -0.5046634, -3.07081, -1.920768, -3.72287), + new CubicCurveTo(-1.4711631, -0.77284, -3.4574873, -0.11153, -4.69154031, -1.40244), + new CubicCurveTo(-1.30616123, -1.40422, -0.5308003, -4.1855799, 1.46313121, -4.4219799), + new CubicCurveTo(1.4290018, -0.25469, 3.1669517, -0.0875, 4.1676818, -1.36207), + new CubicCurveTo(0.9172241, -1.12206, 0.9594176, -2.63766, 1.0685793, -4.01259), + new CubicCurveTo(0.4020299, -1.95732999, 3.2823027, -2.72818999, 4.5638567, -1.15760999), + new CubicCurveTo(1.215789, 1.31824999, 0.738899, 3.90740999, -1.103778, 4.37267999), + new CubicCurveTo(-1.3972543, 0.40868, -3.0929979, 0.0413, -4.2208253, 1.16215), + new CubicCurveTo(-1.3524806, 1.26423, -1.3178578, 3.29187, -1.1086673, 4.9895199), + new CubicCurveTo(0.167826, 1.28946, 1.0091133, 2.5347, 2.3196964, 2.86608), + new CubicCurveTo(1.6253079, 0.53477, 3.4876372, 0.45004, 5.0294052, -0.30121), + new CubicCurveTo(1.335829, -0.81654, 1.666839, -2.49408, 1.717756, -3.9432), + new CubicCurveTo(0.08759, -1.1232899, 0.704887, -2.3061299, 1.871843, -2.5951699), + new CubicCurveTo(1.534558, -0.50726, 3.390804, 0.62784, 3.467269, 2.28631), + new CubicCurveTo(0.183147, 1.4285099, -0.949563, 2.9179999, -2.431156, 2.9383699), + new CubicCurveTo(-1.390597, 0.17337, -3.074035, 0.18128, -3.971365, 1.45069), + new CubicCurveTo(-0.99314, 1.271, -0.676157, 2.98683, -1.1715, 4.43018), + new CubicCurveTo(-0.518248, 1.11436, -1.909118, 1.63902, -3.0700005, 1.37803), + new ClosePath() }; static { - for(int i = 1; i < ELEMS.length; ++i) { + for (int i = 1; i < ELEMS.length; ++i) { ELEMS[i].setAbsolute(false); } } @@ -87,11 +87,11 @@ public class Indicator { step += fw ? 1 : -1; - if(step == steps) { + if (step == steps) { fw = false; lOpacity = 0.0; rOpacity = 1.0; - } else if(step == 0) { + } else if (step == 0) { fw = true; lOpacity = 1.0; rOpacity = 0.0; diff --git a/app/src/main/java/io/xpipe/app/util/LicenseProvider.java b/app/src/main/java/io/xpipe/app/util/LicenseProvider.java index d34590ccc..8fc8844aa 100644 --- a/app/src/main/java/io/xpipe/app/util/LicenseProvider.java +++ b/app/src/main/java/io/xpipe/app/util/LicenseProvider.java @@ -15,26 +15,6 @@ public abstract class LicenseProvider { return INSTANCE; } - public static class Loader implements ModuleLayerLoader { - - @Override - public void init(ModuleLayer layer) { - INSTANCE = ServiceLoader.load(layer, LicenseProvider.class).stream() - .map(ServiceLoader.Provider::get) - .findFirst().orElseThrow(() -> ExtensionException.corrupt("Missing license provider")); - } - - @Override - public boolean requiresFullDaemon() { - return true; - } - - @Override - public boolean prioritizeLoading() { - return true; - } - } - public abstract boolean hasLicense(); public abstract LicensedFeature getFeature(String id); @@ -48,4 +28,25 @@ public abstract class LicenseProvider { public abstract Comp overviewPage(); public abstract boolean hasPaidLicense(); + + public static class Loader implements ModuleLayerLoader { + + @Override + public void init(ModuleLayer layer) { + INSTANCE = ServiceLoader.load(layer, LicenseProvider.class).stream() + .map(ServiceLoader.Provider::get) + .findFirst() + .orElseThrow(() -> ExtensionException.corrupt("Missing license provider")); + } + + @Override + public boolean requiresFullDaemon() { + return true; + } + + @Override + public boolean prioritizeLoading() { + return true; + } + } } diff --git a/app/src/main/java/io/xpipe/app/util/LicenseRequiredException.java b/app/src/main/java/io/xpipe/app/util/LicenseRequiredException.java index abb157e6b..8eb2eda02 100644 --- a/app/src/main/java/io/xpipe/app/util/LicenseRequiredException.java +++ b/app/src/main/java/io/xpipe/app/util/LicenseRequiredException.java @@ -8,11 +8,11 @@ public class LicenseRequiredException extends RuntimeException { private final LicensedFeature feature; public LicenseRequiredException(LicensedFeature feature) { - super(feature.getDisplayName() + (feature.isPlural() ? " are" : " is") + " only supported with a professional license"); + super(feature.getDisplayName() + (feature.isPlural() ? " are" : " is") + + " only supported with a professional license"); this.feature = feature; } - public LicenseRequiredException(String featureName, boolean plural, LicensedFeature feature) { super(featureName + (plural ? " are" : " is") + " only supported with a professional license"); this.feature = feature; diff --git a/app/src/main/java/io/xpipe/app/util/LocalShell.java b/app/src/main/java/io/xpipe/app/util/LocalShell.java index 474e4a6e7..ba8f78193 100644 --- a/app/src/main/java/io/xpipe/app/util/LocalShell.java +++ b/app/src/main/java/io/xpipe/app/util/LocalShell.java @@ -9,6 +9,7 @@ public class LocalShell { @Getter private static ShellControlCache localCache; + private static ShellControl local; private static ShellControl localPowershell; @@ -19,7 +20,8 @@ public class LocalShell { public static ShellControl getLocalPowershell() { if (localPowershell == null) { - localPowershell = ProcessControlProvider.get().createLocalProcessControl(true) + localPowershell = ProcessControlProvider.get() + .createLocalProcessControl(true) .subShell(ShellDialects.POWERSHELL) .start(); } diff --git a/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java b/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java index 12c2a665d..c7ccb09fc 100644 --- a/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java +++ b/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java @@ -56,7 +56,8 @@ public class MacOsPermissions { a.getButtonTypes().clear(); a.getButtonTypes().add(ButtonType.CANCEL); alert.set(a); - }, buttonType -> { + }, + buttonType -> { alert.get().close(); state.set(false); }); diff --git a/app/src/main/java/io/xpipe/app/util/MarkdownBuilder.java b/app/src/main/java/io/xpipe/app/util/MarkdownBuilder.java index 474314c0c..dcf9f9cdb 100644 --- a/app/src/main/java/io/xpipe/app/util/MarkdownBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/MarkdownBuilder.java @@ -6,14 +6,14 @@ import net.steppschuh.markdowngenerator.text.code.CodeBlock; public class MarkdownBuilder { - public static MarkdownBuilder of() { - return new MarkdownBuilder(); - } - private final StringBuilder builder = new StringBuilder(); private MarkdownBuilder() {} + public static MarkdownBuilder of() { + return new MarkdownBuilder(); + } + public MarkdownBuilder add(String t) { builder.append(t); return this; diff --git a/app/src/main/java/io/xpipe/app/util/ObservableDataStore.java b/app/src/main/java/io/xpipe/app/util/ObservableDataStore.java index 358fc8268..3c96a1a9c 100644 --- a/app/src/main/java/io/xpipe/app/util/ObservableDataStore.java +++ b/app/src/main/java/io/xpipe/app/util/ObservableDataStore.java @@ -33,14 +33,14 @@ public interface ObservableDataStore extends DataStore, InternalCacheDataStore { } } - default void setObserverState(boolean state) { - setCache("observerState", state); - } - default boolean getObserverState() { return getCache("observerState", Boolean.class, false); } + default void setObserverState(boolean state) { + setCache("observerState", state); + } + private void refresh() { var entry = DataStorage.get().getStoreEntry(this); DataStorage.get().refreshChildren(entry); diff --git a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java index ffcaf83f4..0b7f50c0a 100644 --- a/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java +++ b/app/src/main/java/io/xpipe/app/util/OptionsBuilder.java @@ -37,10 +37,6 @@ public class OptionsBuilder { private Comp lastCompHeadReference; private ObservableValue lastNameReference; - public Validator buildEffectiveValidator() { - return new ChainedValidator(allValidators); - } - public OptionsBuilder() { this.ownValidator = new SimpleValidator(); this.allValidators.add(ownValidator); @@ -51,14 +47,21 @@ public class OptionsBuilder { this.allValidators.add(ownValidator); } + public Validator buildEffectiveValidator() { + return new ChainedValidator(allValidators); + } + public OptionsBuilder choice(IntegerProperty selectedIndex, Map options) { var list = options.entrySet().stream() .map(e -> new ChoicePaneComp.Entry( - AppI18n.observable(e.getKey()), e.getValue() != null ? e.getValue().buildComp() : Comp.empty())) + AppI18n.observable(e.getKey()), + e.getValue() != null ? e.getValue().buildComp() : Comp.empty())) .toList(); - var validatorList = - options.values().stream().map(builder -> builder != null ? builder.buildEffectiveValidator() : new SimpleValidator()).toList(); - var selected = new SimpleObjectProperty<>(selectedIndex.getValue() != -1 ? list.get(selectedIndex.getValue()) : null); + var validatorList = options.values().stream() + .map(builder -> builder != null ? builder.buildEffectiveValidator() : new SimpleValidator()) + .toList(); + var selected = + new SimpleObjectProperty<>(selectedIndex.getValue() != -1 ? list.get(selectedIndex.getValue()) : null); selected.addListener((observable, oldValue, newValue) -> { selectedIndex.setValue(newValue != null ? list.indexOf(newValue) : null); }); @@ -123,7 +126,6 @@ public class OptionsBuilder { return this; } - public OptionsBuilder addTitle(String titleKey) { finishCurrent(); entries.add(new OptionsComp.Entry( @@ -160,8 +162,8 @@ public class OptionsBuilder { public OptionsBuilder hide(ObservableValue b) { lastCompHeadReference.hide(b); return this; - } - + } + public OptionsBuilder disable(boolean b) { lastCompHeadReference.disable(new SimpleBooleanProperty(b)); return this; @@ -238,7 +240,8 @@ public class OptionsBuilder { } public OptionsBuilder addPath(Property prop) { - var string = new SimpleStringProperty(prop.getValue() != null ? prop.getValue().toString() : null); + var string = new SimpleStringProperty( + prop.getValue() != null ? prop.getValue().toString() : null); var comp = new TextFieldComp(string, true); string.addListener((observable, oldValue, newValue) -> { if (newValue == null) { diff --git a/app/src/main/java/io/xpipe/app/util/PasswordLockSecretValue.java b/app/src/main/java/io/xpipe/app/util/PasswordLockSecretValue.java index 06e434113..1365dfe5a 100644 --- a/app/src/main/java/io/xpipe/app/util/PasswordLockSecretValue.java +++ b/app/src/main/java/io/xpipe/app/util/PasswordLockSecretValue.java @@ -26,6 +26,13 @@ public class PasswordLockSecretValue extends AesSecretValue { return 8192; } + protected SecretKey getAESKey() throws InvalidKeySpecException { + var chars = AppPrefs.get().getLockPassword().getValue() != null + ? AppPrefs.get().getLockPassword().getValue().getSecret() + : new char[0]; + return getSecretKey(chars); + } + @Override public InPlaceSecretValue inPlace() { return new InPlaceSecretValue(getSecret()); @@ -35,11 +42,4 @@ public class PasswordLockSecretValue extends AesSecretValue { public String toString() { return ""; } - - protected SecretKey getAESKey() throws InvalidKeySpecException { - var chars = AppPrefs.get().getLockPassword().getValue() != null - ? AppPrefs.get().getLockPassword().getValue().getSecret() - : new char[0]; - return getSecretKey(chars); - } } diff --git a/app/src/main/java/io/xpipe/app/util/PlatformState.java b/app/src/main/java/io/xpipe/app/util/PlatformState.java index 4a117c717..beea88cbb 100644 --- a/app/src/main/java/io/xpipe/app/util/PlatformState.java +++ b/app/src/main/java/io/xpipe/app/util/PlatformState.java @@ -58,7 +58,6 @@ public enum PlatformState { return current == RUNNING; } - private static Optional initPlatform() { if (current == EXITED) { return Optional.of(new IllegalStateException("Platform has already exited")); @@ -89,10 +88,10 @@ public enum PlatformState { var value = i + "%"; switch (OsType.getLocal()) { case OsType.Linux linux -> { - System.setProperty("glass.gtk.uiScale",value); + System.setProperty("glass.gtk.uiScale", value); } case OsType.Windows windows -> { - System.setProperty("glass.win.uiScale",value); + System.setProperty("glass.win.uiScale", value); } default -> {} } diff --git a/app/src/main/java/io/xpipe/app/util/ProgressScope.java b/app/src/main/java/io/xpipe/app/util/ProgressScope.java index b17690a2e..1a7ad18bd 100644 --- a/app/src/main/java/io/xpipe/app/util/ProgressScope.java +++ b/app/src/main/java/io/xpipe/app/util/ProgressScope.java @@ -11,6 +11,10 @@ public class ProgressScope implements AutoCloseable { private BooleanProperty active = new SimpleBooleanProperty(); private boolean forcePlatform; + public ProgressScope(DoubleProperty prop) { + this.prop = prop; + } + public ProgressScope withActive(BooleanProperty active) { this.active = active; return this; @@ -21,10 +25,6 @@ public class ProgressScope implements AutoCloseable { return this; } - public ProgressScope(DoubleProperty prop) { - this.prop = prop; - } - public ProgressScope start() { if (forcePlatform) { PlatformThread.runLaterIfNeeded(() -> { diff --git a/app/src/main/java/io/xpipe/app/util/ScanAlert.java b/app/src/main/java/io/xpipe/app/util/ScanAlert.java index bd72b0057..018b21106 100644 --- a/app/src/main/java/io/xpipe/app/util/ScanAlert.java +++ b/app/src/main/java/io/xpipe/app/util/ScanAlert.java @@ -65,22 +65,38 @@ public class ScanAlert { }); } + private static void show( + DataStoreEntry initialStore, Function> applicable) { + DialogComp.showWindow( + "scanAlertTitle", + stage -> new Dialog(stage, initialStore != null ? initialStore.ref() : null, applicable)); + } + private static class Dialog extends DialogComp { private final DataStoreEntryRef initialStore; private final Function> applicable; private final Stage window; private final ObjectProperty> entry; - private final ListProperty selected = new SimpleListProperty<>(FXCollections.observableArrayList()); + private final ListProperty selected = + new SimpleListProperty<>(FXCollections.observableArrayList()); private final BooleanProperty busy = new SimpleBooleanProperty(); - private Dialog(Stage window, DataStoreEntryRef entry, Function> applicable) { + private Dialog( + Stage window, + DataStoreEntryRef entry, + Function> applicable) { this.window = window; this.initialStore = entry; this.entry = new SimpleObjectProperty<>(entry); this.applicable = applicable; } + @Override + protected ObservableValue busy() { + return busy; + } + @Override protected void finish() { ThreadHelper.runAsync(() -> { @@ -99,7 +115,9 @@ public class ScanAlert { for (var a : copy) { // If the user decided to remove the selected entry // while the scan is running, just return instantly - if (!DataStorage.get().getStoreEntriesSet().contains(entry.get().get())) { + if (!DataStorage.get() + .getStoreEntriesSet() + .contains(entry.get().get())) { return; } @@ -113,24 +131,32 @@ public class ScanAlert { }); } - @Override - protected ObservableValue busy() { - return busy; - } - @Override public Comp content() { StackPane stackPane = new StackPane(); stackPane.getStyleClass().add("scan-list"); - var b = new OptionsBuilder().name("scanAlertChoiceHeader").description("scanAlertChoiceHeaderDescription").addComp( - new DataStoreChoiceComp<>(DataStoreChoiceComp.Mode.OTHER, null, entry, ShellStore.class, store1 -> true, - StoreViewState.get().getAllConnectionsCategory()).disable( - new SimpleBooleanProperty(initialStore != null))).name("scanAlertHeader").description( - "scanAlertHeaderDescription").addComp(Comp.of(() -> stackPane).vgrow()).buildComp().prefWidth(500).prefHeight( - 650).apply(struc -> { - VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS); - }).padding(new Insets(20)); + var b = new OptionsBuilder() + .name("scanAlertChoiceHeader") + .description("scanAlertChoiceHeaderDescription") + .addComp(new DataStoreChoiceComp<>( + DataStoreChoiceComp.Mode.OTHER, + null, + entry, + ShellStore.class, + store1 -> true, + StoreViewState.get().getAllConnectionsCategory()) + .disable(new SimpleBooleanProperty(initialStore != null))) + .name("scanAlertHeader") + .description("scanAlertHeaderDescription") + .addComp(Comp.of(() -> stackPane).vgrow()) + .buildComp() + .prefWidth(500) + .prefHeight(650) + .apply(struc -> { + VBox.setVgrow(struc.get().getChildren().get(1), ALWAYS); + }) + .padding(new Insets(20)); SimpleChangeListener.apply(entry, newValue -> { selected.clear(); @@ -150,9 +176,17 @@ public class ScanAlert { return; } - selected.setAll(a.stream().filter(scanOperation -> scanOperation.isDefaultSelected() && !scanOperation.isDisabled()).toList()); - var r = new ListSelectorComp<>(a, scanOperation -> AppI18n.get(scanOperation.getNameKey()), selected, scanOperation -> scanOperation.isDisabled(), - a.size() > 3).createRegion(); + selected.setAll(a.stream() + .filter(scanOperation -> + scanOperation.isDefaultSelected() && !scanOperation.isDisabled()) + .toList()); + var r = new ListSelectorComp<>( + a, + scanOperation -> AppI18n.get(scanOperation.getNameKey()), + selected, + scanOperation -> scanOperation.isDisabled(), + a.size() > 3) + .createRegion(); stackPane.getChildren().add(r); }); }); @@ -162,12 +196,4 @@ public class ScanAlert { return b; } } - - private static void show( - DataStoreEntry initialStore, Function> applicable - ) { - DialogComp.showWindow("scanAlertTitle", stage -> - new Dialog(stage, initialStore != null ? initialStore.ref() : null, - applicable)); - } } diff --git a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java index 366745f8f..1c51c1c64 100644 --- a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java +++ b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java @@ -27,7 +27,13 @@ public class ScriptHelper { } } - public static String constructTerminalInitFile(ShellDialect t, ShellControl processControl, FailableFunction workingDirectory, List init, String toExecuteInShell, TerminalInitScriptConfig config) + public static String constructTerminalInitFile( + ShellDialect t, + ShellControl processControl, + FailableFunction workingDirectory, + List init, + String toExecuteInShell, + TerminalInitScriptConfig config) throws Exception { String nl = t.getNewLine().getNewLineString(); var content = ""; @@ -50,7 +56,7 @@ public class ScriptHelper { } if (config.getDisplayName() != null) { - content += nl + t.changeTitleCommand(config.getDisplayName()) + nl; + content += nl + t.changeTitleCommand(config.getDisplayName()) + nl; } if (workingDirectory != null) { @@ -127,22 +133,24 @@ public class ScriptHelper { var file = FileNames.join(temp, fileName); if (type != parent.getShellDialect()) { try (var sub = parent.subShell(type).start()) { - var content = sub.getShellDialect().getAskpass().prepareStderrPassthroughContent(sub, requestId, prefix); + var content = + sub.getShellDialect().getAskpass().prepareStderrPassthroughContent(sub, requestId, prefix); return createExecScript(sub.getShellDialect(), sub, file, content); } } else { - var content = parent.getShellDialect().getAskpass().prepareStderrPassthroughContent(parent, requestId, prefix); + var content = + parent.getShellDialect().getAskpass().prepareStderrPassthroughContent(parent, requestId, prefix); return createExecScript(parent.getShellDialect(), parent, file, content); } } - public static String createTerminalPreparedAskpassScript(SecretValue pass, ShellControl parent, boolean forceExecutable) - throws Exception { + public static String createTerminalPreparedAskpassScript( + SecretValue pass, ShellControl parent, boolean forceExecutable) throws Exception { return createTerminalPreparedAskpassScript(pass != null ? List.of(pass) : List.of(), parent, forceExecutable); } - public static String createTerminalPreparedAskpassScript(List pass, ShellControl parent, boolean forceExecutable) - throws Exception { + public static String createTerminalPreparedAskpassScript( + List pass, ShellControl parent, boolean forceExecutable) throws Exception { var scriptType = parent.getShellDialect(); // Fix for powershell as there are permission issues when executing a powershell askpass script @@ -153,14 +161,15 @@ public class ScriptHelper { return createTerminalPreparedAskpassScript(pass, parent, scriptType); } - private static String createTerminalPreparedAskpassScript(List pass, ShellControl parent, ShellDialect type) - throws Exception { + private static String createTerminalPreparedAskpassScript( + List pass, ShellControl parent, ShellDialect type) throws Exception { var fileName = "exec-" + getScriptId() + "." + type.getScriptFileEnding(); var temp = parent.getSystemTemporaryDirectory(); var file = FileNames.join(temp, fileName); if (type != parent.getShellDialect()) { try (var sub = parent.subShell(type).start()) { - var content = sub.getShellDialect().getAskpass() + var content = sub.getShellDialect() + .getAskpass() .prepareFixedContent( sub, file, @@ -170,7 +179,8 @@ public class ScriptHelper { return createExecScript(sub.getShellDialect(), sub, file, content); } } else { - var content = parent.getShellDialect().getAskpass() + var content = parent.getShellDialect() + .getAskpass() .prepareFixedContent( parent, file, diff --git a/app/src/main/java/io/xpipe/app/util/SecretManager.java b/app/src/main/java/io/xpipe/app/util/SecretManager.java index 4770193fc..01896ed52 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretManager.java +++ b/app/src/main/java/io/xpipe/app/util/SecretManager.java @@ -15,24 +15,33 @@ public class SecretManager { public static Optional getProgress(UUID requestId, UUID storeId) { return progress.stream() - .filter(secretQueryProgress -> secretQueryProgress.getRequestId().equals(requestId) && - secretQueryProgress.getStoreId().equals(storeId)) + .filter(secretQueryProgress -> + secretQueryProgress.getRequestId().equals(requestId) + && secretQueryProgress.getStoreId().equals(storeId)) .findFirst(); } public static Optional getProgress(UUID requestId) { return progress.stream() - .filter(secretQueryProgress -> secretQueryProgress.getRequestId().equals(requestId)) + .filter(secretQueryProgress -> + secretQueryProgress.getRequestId().equals(requestId)) .findFirst(); } - public static SecretQueryProgress expectElevationPrompt(UUID request, UUID secretId, CountDown countDown, boolean askIfNeeded) { - var p = new SecretQueryProgress(request, secretId, List.of(askIfNeeded ? SecretQuery.elevation(secretId) : SecretQuery.prompt(true)), SecretQuery.prompt(false), countDown); + public static SecretQueryProgress expectElevationPrompt( + UUID request, UUID secretId, CountDown countDown, boolean askIfNeeded) { + var p = new SecretQueryProgress( + request, + secretId, + List.of(askIfNeeded ? SecretQuery.elevation(secretId) : SecretQuery.prompt(true)), + SecretQuery.prompt(false), + countDown); progress.add(p); return p; } - public static SecretQueryProgress expectAskpass(UUID request, UUID storeId, List suppliers, SecretQuery fallback, CountDown countDown) { + public static SecretQueryProgress expectAskpass( + UUID request, UUID storeId, List suppliers, SecretQuery fallback, CountDown countDown) { var p = new SecretQueryProgress(request, storeId, suppliers, fallback, countDown); progress.add(p); return p; @@ -61,14 +70,19 @@ public class SecretManager { } public static void completeRequest(UUID request) { - if (progress.removeIf(secretQueryProgress -> secretQueryProgress.getRequestId().equals(request))) { - TrackEvent.withTrace("Completed secret request").tag("uuid", request).handle(); + if (progress.removeIf( + secretQueryProgress -> secretQueryProgress.getRequestId().equals(request))) { + TrackEvent.withTrace("Completed secret request") + .tag("uuid", request) + .handle(); } } public static void clearAll(Object store) { var id = UuidHelper.generateFromObject(store); - secrets.entrySet().removeIf(secretReferenceSecretValueEntry -> secretReferenceSecretValueEntry.getKey().getSecretId().equals(id)); + secrets.entrySet() + .removeIf(secretReferenceSecretValueEntry -> + secretReferenceSecretValueEntry.getKey().getSecretId().equals(id)); } public static void clear(SecretReference ref) { diff --git a/app/src/main/java/io/xpipe/app/util/SecretQuery.java b/app/src/main/java/io/xpipe/app/util/SecretQuery.java index 9acef8cea..5cfd47068 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretQuery.java +++ b/app/src/main/java/io/xpipe/app/util/SecretQuery.java @@ -11,11 +11,6 @@ public interface SecretQuery { static SecretQuery elevation(UUID secretId) { return new SecretQuery() { - @Override - public SecretQueryResult query(String prompt) { - return AskpassAlert.queryRaw(prompt, null); - } - @Override public Optional retrieveCache(String prompt, SecretReference reference) { var found = SecretQuery.super.retrieveCache(prompt, reference); @@ -33,6 +28,11 @@ public interface SecretQuery { return r.isCancelled() ? Optional.empty() : found; } + @Override + public SecretQueryResult query(String prompt) { + return AskpassAlert.queryRaw(prompt, null); + } + @Override public boolean cache() { return true; diff --git a/app/src/main/java/io/xpipe/app/util/SecretQueryProgress.java b/app/src/main/java/io/xpipe/app/util/SecretQueryProgress.java index fe8c89e17..84fb81cc3 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretQueryProgress.java +++ b/app/src/main/java/io/xpipe/app/util/SecretQueryProgress.java @@ -7,7 +7,9 @@ import io.xpipe.core.util.SecretValue; import lombok.Getter; import lombok.NonNull; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; @Getter public class SecretQueryProgress { @@ -20,7 +22,12 @@ public class SecretQueryProgress { private final CountDown countDown; private boolean requestCancelled; - public SecretQueryProgress(@NonNull UUID requestId, @NonNull UUID storeId, @NonNull List suppliers, @NonNull SecretQuery fallback, @NonNull CountDown countDown) { + public SecretQueryProgress( + @NonNull UUID requestId, + @NonNull UUID storeId, + @NonNull List suppliers, + @NonNull SecretQuery fallback, + @NonNull CountDown countDown) { this.requestId = requestId; this.storeId = storeId; this.suppliers = new ArrayList<>(suppliers); @@ -61,7 +68,9 @@ public class SecretQueryProgress { var ref = new SecretReference(storeId, firstSeenIndex); var sup = suppliers.get(firstSeenIndex); - var shouldCache = sup.cache() && SecretManager.shouldCacheForPrompt(prompt) && !AppPrefs.get().dontCachePasswords().get(); + var shouldCache = sup.cache() + && SecretManager.shouldCacheForPrompt(prompt) + && !AppPrefs.get().dontCachePasswords().get(); var wasLastPrompt = firstSeenIndex == seenPrompts.size() - 1; // Clear cache if secret was wrong/queried again diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java index 77cd76135..c7c612633 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategy.java @@ -28,7 +28,6 @@ public interface SecretRetrievalStrategy { return true; } - @JsonTypeName("none") class None implements SecretRetrievalStrategy { @@ -59,7 +58,8 @@ public interface SecretRetrievalStrategy { return new SecretQuery() { @Override public SecretQueryResult query(String prompt) { - return new SecretQueryResult(value != null ? value.getInternalSecret() : InPlaceSecretValue.of(""), false); + return new SecretQueryResult( + value != null ? value.getInternalSecret() : InPlaceSecretValue.of(""), false); } @Override @@ -120,7 +120,8 @@ public interface SecretRetrievalStrategy { try (var cc = new LocalStore().control().command(cmd).start()) { return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false); } catch (Exception ex) { - ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex).handle(); + ErrorEvent.fromThrowable("Unable to retrieve password with command " + cmd, ex) + .handle(); return new SecretQueryResult(null, true); } } @@ -154,7 +155,8 @@ public interface SecretRetrievalStrategy { try (var cc = new LocalStore().control().command(command).start()) { return new SecretQueryResult(InPlaceSecretValue.of(cc.readStdoutOrThrow()), false); } catch (Exception ex) { - ErrorEvent.fromThrowable("Unable to retrieve password with command " + command, ex).handle(); + ErrorEvent.fromThrowable("Unable to retrieve password with command " + command, ex) + .handle(); return new SecretQueryResult(null, true); } } diff --git a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java index 8471a337b..96a659bf8 100644 --- a/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java +++ b/app/src/main/java/io/xpipe/app/util/SecretRetrievalStrategyHelper.java @@ -20,7 +20,10 @@ public class SecretRetrievalStrategyHelper { private static OptionsBuilder inPlace(Property p) { var original = p.getValue() != null ? p.getValue().getValue() : null; - var secretProperty = new SimpleObjectProperty<>(p.getValue() != null && p.getValue().getValue() != null ? p.getValue().getValue().getInternalSecret() : null); + var secretProperty = new SimpleObjectProperty<>( + p.getValue() != null && p.getValue().getValue() != null + ? p.getValue().getValue().getInternalSecret() + : null); return new OptionsBuilder() .addComp(new SecretFieldComp(secretProperty), secretProperty) .bind( @@ -29,7 +32,8 @@ public class SecretRetrievalStrategyHelper { var changed = !Arrays.equals( newSecret != null ? newSecret.getSecret() : new char[0], original != null ? original.getSecret() : new char[0]); - return new SecretRetrievalStrategy.InPlace(changed ? new DataStoreSecret(secretProperty.getValue()) : original); + return new SecretRetrievalStrategy.InPlace( + changed ? new DataStoreSecret(secretProperty.getValue()) : original); }, p); } @@ -38,7 +42,9 @@ public class SecretRetrievalStrategyHelper { var keyProperty = new SimpleObjectProperty<>(p.getValue() != null ? p.getValue().getKey() : null); var content = new HorizontalComp(List.of( - new TextFieldComp(keyProperty).apply(struc -> struc.get().setPromptText("Password key")).hgrow(), + new TextFieldComp(keyProperty) + .apply(struc -> struc.get().setPromptText("Password key")) + .hgrow(), new ButtonComp(null, new FontIcon("mdomz-settings"), () -> { AppPrefs.get().selectCategory(9); App.getApp().getStage().requestFocus(); @@ -102,7 +108,8 @@ public class SecretRetrievalStrategyHelper { .bindChoice( () -> { return switch (selected.get() - offset) { - case 0 -> new SimpleObjectProperty<>(allowNone ? new SecretRetrievalStrategy.None() : null); + case 0 -> new SimpleObjectProperty<>( + allowNone ? new SecretRetrievalStrategy.None() : null); case 1 -> inPlace; case 2 -> passwordManager; case 3 -> customCommand; diff --git a/app/src/main/java/io/xpipe/app/util/SimpleValidator.java b/app/src/main/java/io/xpipe/app/util/SimpleValidator.java index d3e308c61..cbbc9c68a 100644 --- a/app/src/main/java/io/xpipe/app/util/SimpleValidator.java +++ b/app/src/main/java/io/xpipe/app/util/SimpleValidator.java @@ -98,19 +98,6 @@ public class SimpleValidator implements Validator { return !containsErrors(); } - private void refreshProperties() { - ValidationResult nextResult = new ValidationResult(); - for (Check check : checks.keySet()) { - nextResult.addAll(check.getValidationResult().getMessages()); - } - validationResultProperty.set(nextResult); - boolean hasErrors = false; - for (ValidationMessage msg : nextResult.getMessages()) { - hasErrors = hasErrors || msg.getSeverity() == Severity.ERROR; - } - containsErrorsProperty.set(hasErrors); - } - /** * Create a string property that depends on the validation result. * Each error message will be displayed on a separate line prefixed with a bullet. @@ -134,4 +121,17 @@ public class SimpleValidator implements Validator { }, validationResultProperty); } + + private void refreshProperties() { + ValidationResult nextResult = new ValidationResult(); + for (Check check : checks.keySet()) { + nextResult.addAll(check.getValidationResult().getMessages()); + } + validationResultProperty.set(nextResult); + boolean hasErrors = false; + for (ValidationMessage msg : nextResult.getMessages()) { + hasErrors = hasErrors || msg.getSeverity() == Severity.ERROR; + } + containsErrorsProperty.set(hasErrors); + } } diff --git a/app/src/main/java/io/xpipe/app/util/TerminalLauncher.java b/app/src/main/java/io/xpipe/app/util/TerminalLauncher.java index 63c6192e6..bb7503443 100644 --- a/app/src/main/java/io/xpipe/app/util/TerminalLauncher.java +++ b/app/src/main/java/io/xpipe/app/util/TerminalLauncher.java @@ -20,7 +20,8 @@ public class TerminalLauncher { throw ErrorEvent.unreportable(new IllegalStateException(AppI18n.get("noTerminalSet"))); } var script = ScriptHelper.createLocalExecScript(command); - var config = new ExternalTerminalType.LaunchConfiguration(null, title, title, script, shellControl.getShellDialect()); + var config = new ExternalTerminalType.LaunchConfiguration( + null, title, title, script, shellControl.getShellDialect()); type.launch(config); } @@ -35,9 +36,7 @@ public class TerminalLauncher { } var color = entry != null ? DataStorage.get().getRootForEntry(entry).getColor() : null; - var prefix = entry != null && color != null && type.supportsColoredTitle() - ? color.getEmoji() + " " - : ""; + var prefix = entry != null && color != null && type.supportsColoredTitle() ? color.getEmoji() + " " : ""; var cleanTitle = (title != null ? title : entry != null ? entry.getName() : "?"); var adjustedTitle = prefix + cleanTitle; var terminalConfig = new TerminalInitScriptConfig( @@ -49,9 +48,9 @@ public class TerminalLauncher { var d = LocalShell.getShell().getShellDialect(); var launcherScript = d.terminalLauncherScript(request, adjustedTitle); var preparationScript = ScriptHelper.createLocalExecScript(launcherScript); - var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? color : null, adjustedTitle, cleanTitle, preparationScript, - d); - var latch = TerminalLauncherManager.submitAsync(request,cc,terminalConfig,directory); + var config = new ExternalTerminalType.LaunchConfiguration( + entry != null ? color : null, adjustedTitle, cleanTitle, preparationScript, d); + var latch = TerminalLauncherManager.submitAsync(request, cc, terminalConfig, directory); try { type.launch(config); latch.await(); diff --git a/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java b/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java index 3be1e9832..aa0d56e3c 100644 --- a/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java +++ b/app/src/main/java/io/xpipe/app/util/TerminalLauncherManager.java @@ -18,7 +18,8 @@ public class TerminalLauncherManager { private static final Map entries = new ConcurrentHashMap<>(); - private static void prepare(ProcessControl processControl, TerminalInitScriptConfig config, String directory, Entry entry) { + private static void prepare( + ProcessControl processControl, TerminalInitScriptConfig config, String directory, Entry entry) { try { var file = ScriptHelper.createLocalExecScript( processControl.prepareTerminalOpen(config, directory != null ? var1 -> directory : null)); @@ -28,17 +29,15 @@ public class TerminalLauncherManager { } } - public static void submitSync(UUID request, - ProcessControl processControl, TerminalInitScriptConfig config, String directory - ) { + public static void submitSync( + UUID request, ProcessControl processControl, TerminalInitScriptConfig config, String directory) { var entry = new Entry(request, processControl, config, directory, null); entries.put(request, entry); prepare(processControl, config, directory, entry); } - public static CountDownLatch submitAsync(UUID request, - ProcessControl processControl, TerminalInitScriptConfig config, String directory - ) { + public static CountDownLatch submitAsync( + UUID request, ProcessControl processControl, TerminalInitScriptConfig config, String directory) { var entry = new Entry(request, processControl, config, directory, null); entries.put(request, entry); var latch = new CountDownLatch(1); diff --git a/app/src/main/java/io/xpipe/app/util/ThreadHelper.java b/app/src/main/java/io/xpipe/app/util/ThreadHelper.java index 98945d63d..ee4664b3c 100644 --- a/app/src/main/java/io/xpipe/app/util/ThreadHelper.java +++ b/app/src/main/java/io/xpipe/app/util/ThreadHelper.java @@ -24,7 +24,9 @@ public class ThreadHelper { } public static Thread unstarted(Runnable r) { - return AppProperties.get().isUseVirtualThreads() ? Thread.ofVirtual().unstarted(wrap(r)) : Thread.ofPlatform().unstarted(wrap(r)); + return AppProperties.get().isUseVirtualThreads() + ? Thread.ofVirtual().unstarted(wrap(r)) + : Thread.ofPlatform().unstarted(wrap(r)); } public static Thread runAsync(Runnable r) { diff --git a/app/src/main/java/io/xpipe/app/util/UnlockAlert.java b/app/src/main/java/io/xpipe/app/util/UnlockAlert.java index b1c49b17a..5557a527f 100644 --- a/app/src/main/java/io/xpipe/app/util/UnlockAlert.java +++ b/app/src/main/java/io/xpipe/app/util/UnlockAlert.java @@ -1,6 +1,9 @@ package io.xpipe.app.util; -import io.xpipe.app.core.*; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.AppStyle; +import io.xpipe.app.core.AppTheme; +import io.xpipe.app.core.AppWindowHelper; import io.xpipe.app.fxcomps.impl.SecretFieldComp; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.prefs.AppPrefs; @@ -58,7 +61,11 @@ public class UnlockAlert { .ifPresentOrElse(t -> {}, () -> canceled.set(true)); if (canceled.get()) { - ErrorEvent.fromMessage("Unlock cancelled").expected().term().omit().handle(); + ErrorEvent.fromMessage("Unlock cancelled") + .expected() + .term() + .omit() + .handle(); return; } diff --git a/app/src/main/java/io/xpipe/app/util/UserConfig.java b/app/src/main/java/io/xpipe/app/util/UserConfig.java index ce8a5d77f..a81691adc 100644 --- a/app/src/main/java/io/xpipe/app/util/UserConfig.java +++ b/app/src/main/java/io/xpipe/app/util/UserConfig.java @@ -1,4 +1,3 @@ package io.xpipe.app.util; -public class UserConfig { -} +public class UserConfig {} diff --git a/app/src/main/java/io/xpipe/app/util/VaultKeySecretValue.java b/app/src/main/java/io/xpipe/app/util/VaultKeySecretValue.java index 43fd437ab..2fe8c632d 100644 --- a/app/src/main/java/io/xpipe/app/util/VaultKeySecretValue.java +++ b/app/src/main/java/io/xpipe/app/util/VaultKeySecretValue.java @@ -26,6 +26,11 @@ public class VaultKeySecretValue extends AesSecretValue { return 8192; } + protected SecretKey getAESKey() throws InvalidKeySpecException { + var chars = DataStorage.get() != null ? DataStorage.get().getVaultKey().toCharArray() : new char[0]; + return getSecretKey(chars); + } + @Override public InPlaceSecretValue inPlace() { return new InPlaceSecretValue(getSecret()); @@ -35,11 +40,4 @@ public class VaultKeySecretValue extends AesSecretValue { public String toString() { return ""; } - - protected SecretKey getAESKey() throws InvalidKeySpecException { - var chars = DataStorage.get() != null - ? DataStorage.get().getVaultKey().toCharArray() - : new char[0]; - return getSecretKey(chars); - } } diff --git a/app/src/main/java/io/xpipe/app/util/WindowsRegistry.java b/app/src/main/java/io/xpipe/app/util/WindowsRegistry.java index 1354c7541..318e6cbae 100644 --- a/app/src/main/java/io/xpipe/app/util/WindowsRegistry.java +++ b/app/src/main/java/io/xpipe/app/util/WindowsRegistry.java @@ -32,7 +32,9 @@ public class WindowsRegistry { // This can fail even with errors in case the jna native library extraction fails try { if (!Advapi32Util.registryValueExists( - hkey == HKEY_LOCAL_MACHINE ? WinReg.HKEY_LOCAL_MACHINE : WinReg.HKEY_CURRENT_USER, key, valueName)) { + hkey == HKEY_LOCAL_MACHINE ? WinReg.HKEY_LOCAL_MACHINE : WinReg.HKEY_CURRENT_USER, + key, + valueName)) { return Optional.empty(); } @@ -44,8 +46,13 @@ public class WindowsRegistry { } } - public static Optional readRemoteString(ShellControl shellControl, int hkey, String key, String valueName) throws Exception { - var command = CommandBuilder.of().add("reg", "query").addQuoted((hkey == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER") + "\\" + key).add("/v").addQuoted(valueName); + public static Optional readRemoteString(ShellControl shellControl, int hkey, String key, String valueName) + throws Exception { + var command = CommandBuilder.of() + .add("reg", "query") + .addQuoted((hkey == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE" : "HKEY_CURRENT_USER") + "\\" + key) + .add("/v") + .addQuoted(valueName); String output; try (var c = shellControl.command(command).start()) { diff --git a/app/src/main/java/io/xpipe/app/util/XPipeSession.java b/app/src/main/java/io/xpipe/app/util/XPipeSession.java index 4f85b3b04..bde878670 100644 --- a/app/src/main/java/io/xpipe/app/util/XPipeSession.java +++ b/app/src/main/java/io/xpipe/app/util/XPipeSession.java @@ -8,21 +8,17 @@ import java.util.UUID; @Value public class XPipeSession { + private static XPipeSession INSTANCE; boolean isNewBuildSession; - /** * Unique identifier that resets on every XPipe restart. */ UUID sessionId; - /** * Unique identifier that resets on every XPipe update. */ UUID buildSessionId; - - private static XPipeSession INSTANCE; - public static void init(UUID buildSessionId) { if (INSTANCE != null) { return; diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index 26a388e9a..b5d542e15 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -3,7 +3,10 @@ import io.xpipe.app.browser.action.BrowserAction; import io.xpipe.app.core.AppLogs; import io.xpipe.app.exchange.*; import io.xpipe.app.exchange.cli.*; -import io.xpipe.app.ext.*; +import io.xpipe.app.ext.ActionProvider; +import io.xpipe.app.ext.DataStoreProvider; +import io.xpipe.app.ext.PrefsProvider; +import io.xpipe.app.ext.ScanProvider; import io.xpipe.app.issue.EventHandler; import io.xpipe.app.issue.EventHandlerImpl; import io.xpipe.app.storage.DataStateProviderImpl; @@ -108,7 +111,8 @@ open module io.xpipe.app { uses LicenseProvider; uses io.xpipe.app.util.LicensedFeature; - provides Module with StorageJacksonModule; + provides Module with + StorageJacksonModule; provides ModuleLayerLoader with ActionProvider.Loader, PrefsProvider.Loader, @@ -140,7 +144,8 @@ open module io.xpipe.app { RenameStoreExchangeImpl, ListStoresExchangeImpl, StoreAddExchangeImpl, - AskpassExchangeImpl, TerminalWaitExchangeImpl, + AskpassExchangeImpl, + TerminalWaitExchangeImpl, TerminalLaunchExchangeImpl, QueryStoreExchangeImpl, WriteStreamExchangeImpl, diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java index 4329147c1..bcef6b4a1 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconClient.java @@ -81,6 +81,17 @@ public class BeaconClient implements AutoCloseable { // }) // } + @Override + public void close() throws ConnectorException { + try { + getRawInputStream().readAllBytes(); + } catch (IOException ex) { + throw new ConnectorException(ex); + } + + super.close(); + } + @Override public T receiveResponse() throws ConnectorException, ClientException, ServerException { @@ -93,17 +104,6 @@ public class BeaconClient implements AutoCloseable { return super.receiveResponse(); } - - @Override - public void close() throws ConnectorException { - try { - getRawInputStream().readAllBytes(); - } catch (IOException ex) { - throw new ConnectorException(ex); - } - - super.close(); - } }; } @@ -314,7 +314,6 @@ public class BeaconClient implements AutoCloseable { } } - @JsonTypeName("daemon") @Value @Builder diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java b/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java index 2e858e399..9b7113f87 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconConfig.java @@ -10,12 +10,12 @@ public class BeaconConfig { public static final byte[] BODY_SEPARATOR = "\n\n".getBytes(StandardCharsets.UTF_8); public static final String BEACON_PORT_PROP = "io.xpipe.beacon.port"; + public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs"; private static final String PRINT_MESSAGES_PROPERTY = "io.xpipe.beacon.printMessages"; private static final String LAUNCH_DAEMON_IN_DEBUG_PROP = "io.xpipe.beacon.launchDebugDaemon"; private static final String ATTACH_DEBUGGER_PROP = "io.xpipe.beacon.attachDebuggerToDaemon"; private static final String EXEC_DEBUG_PROP = "io.xpipe.beacon.printDaemonOutput"; private static final String EXEC_PROCESS_PROP = "io.xpipe.beacon.customDaemonCommand"; - public static final String DAEMON_ARGUMENTS_PROP = "io.xpipe.beacon.daemonArgs"; private static final String LOCAL_PROXY_PROP = "io.xpipe.beacon.localProxy"; public static boolean localProxy() { diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java b/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java index 1ca9f29f6..029f2305b 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconFormat.java @@ -17,17 +17,6 @@ public class BeaconFormat { private final byte[] currentBytes = new byte[SEGMENT_SIZE]; private int index; - @Override - public void close() throws IOException { - if (isClosed()) { - return; - } - - finishBlock(); - out.flush(); - index = -1; - } - @Override public void write(int b) throws IOException { if (isClosed()) { @@ -42,6 +31,17 @@ public class BeaconFormat { index++; } + @Override + public void close() throws IOException { + if (isClosed()) { + return; + } + + finishBlock(); + out.flush(); + index = -1; + } + private boolean isClosed() { return index == -1; } diff --git a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java index 0d2451555..b151ef9ba 100644 --- a/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java +++ b/beacon/src/main/java/io/xpipe/beacon/BeaconServer.java @@ -1,8 +1,8 @@ package io.xpipe.beacon; import io.xpipe.beacon.exchange.StopExchange; -import io.xpipe.core.store.FileNames; import io.xpipe.core.process.OsType; +import io.xpipe.core.store.FileNames; import io.xpipe.core.util.XPipeDaemonMode; import io.xpipe.core.util.XPipeInstallation; diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java index 6879dd36c..93621749c 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/DrainExchange.java @@ -29,6 +29,5 @@ public class DrainExchange implements MessageExchange { @Jacksonized @Builder @Value - public static class Response implements ResponseMessage { - } + public static class Response implements ResponseMessage {} } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java index 1bcce0ddd..f79ff2b71 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/SinkExchange.java @@ -29,6 +29,5 @@ public class SinkExchange implements MessageExchange { @Jacksonized @Builder @Value - public static class Response implements ResponseMessage { - } + public static class Response implements ResponseMessage {} } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java index cce8c89c4..0f4571fc9 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalLaunchExchange.java @@ -29,6 +29,7 @@ public class TerminalLaunchExchange implements MessageExchange { @Builder @Value public static class Response implements ResponseMessage { - @NonNull Path targetFile; + @NonNull + Path targetFile; } } diff --git a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java b/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java index a341003a4..7dab79a91 100644 --- a/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java +++ b/beacon/src/main/java/io/xpipe/beacon/exchange/TerminalWaitExchange.java @@ -27,6 +27,5 @@ public class TerminalWaitExchange implements MessageExchange { @Jacksonized @Builder @Value - public static class Response implements ResponseMessage { - } + public static class Response implements ResponseMessage {} } diff --git a/beacon/src/main/java/module-info.java b/beacon/src/main/java/module-info.java index cdbc674c5..eac7269bf 100644 --- a/beacon/src/main/java/module-info.java +++ b/beacon/src/main/java/module-info.java @@ -42,7 +42,8 @@ open module io.xpipe.beacon { RemoveStoreExchange, StoreAddExchange, ReadDrainExchange, - AskpassExchange, TerminalWaitExchange, + AskpassExchange, + TerminalWaitExchange, TerminalLaunchExchange, ListStoresExchange, DialogExchange, diff --git a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java index 4f340a9e6..9779a1bde 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java +++ b/core/src/main/java/io/xpipe/core/charsetter/Charsetter.java @@ -75,8 +75,7 @@ public abstract class Charsetter { return new BufferedReader(new InputStreamReader(stream, charset.getCharset())); } - public abstract Result read( - FailableSupplier in, FailableConsumer con); + public abstract Result read(FailableSupplier in, FailableConsumer con); public Result detect(StreamDataStore store) throws Exception { Result result = new Result(null, null); @@ -106,13 +105,13 @@ public abstract class Charsetter { } } -// if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof ShellStore m) { -// if (result.getNewLine() == null) { -// result = new Result( -// result.getCharset(), -// m.getShellType() != null ? m.getShellType().getNewLine() : null); -// } -// } + // if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof ShellStore m) { + // if (result.getNewLine() == null) { + // result = new Result( + // result.getCharset(), + // m.getShellType() != null ? m.getShellType().getNewLine() : null); + // } + // } if (result.getCharset() == null) { result = new Result(StreamCharset.UTF8, result.getNewLine()); diff --git a/core/src/main/java/io/xpipe/core/charsetter/NewLine.java b/core/src/main/java/io/xpipe/core/charsetter/NewLine.java index d1b3e062a..04a5804b4 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/NewLine.java +++ b/core/src/main/java/io/xpipe/core/charsetter/NewLine.java @@ -12,6 +12,7 @@ public enum NewLine { CRLF("\r\n", "crlf"); private final String newLine; + @Getter private final String id; @@ -37,5 +38,4 @@ public enum NewLine { public String getNewLineString() { return newLine; } - } diff --git a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java index 038c4d797..9de184bce 100644 --- a/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java +++ b/core/src/main/java/io/xpipe/core/charsetter/StreamCharset.java @@ -120,17 +120,6 @@ public class StreamCharset { byte[] byteOrderMark; List names; - public Reader reader(InputStream stream) throws Exception { - if (hasByteOrderMark()) { - var bom = stream.readNBytes(getByteOrderMark().length); - if (bom.length != 0 && !Arrays.equals(bom, getByteOrderMark())) { - throw new IllegalStateException("Charset does not match: " + charset.toString()); - } - } - - return new InputStreamReader(stream, charset); - } - public static StreamCharset get(Charset charset, boolean byteOrderMark) { return ALL.stream() .filter(streamCharset -> @@ -150,6 +139,24 @@ public class StreamCharset { return found.get(); } + public Reader reader(InputStream stream) throws Exception { + if (hasByteOrderMark()) { + var bom = stream.readNBytes(getByteOrderMark().length); + if (bom.length != 0 && !Arrays.equals(bom, getByteOrderMark())) { + throw new IllegalStateException("Charset does not match: " + charset.toString()); + } + } + + return new InputStreamReader(stream, charset); + } + + @Override + public int hashCode() { + int result = Objects.hash(charset); + result = 31 * result + Arrays.hashCode(byteOrderMark); + return result; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -161,13 +168,6 @@ public class StreamCharset { return charset.equals(that.charset) && Arrays.equals(byteOrderMark, that.byteOrderMark); } - @Override - public int hashCode() { - int result = Objects.hash(charset); - result = 31 * result + Arrays.hashCode(byteOrderMark); - return result; - } - public String toString() { return getNames().getFirst(); } diff --git a/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java b/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java index b2c41c924..f6f14b918 100644 --- a/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/BaseQueryElement.java @@ -13,8 +13,10 @@ import lombok.ToString; public class BaseQueryElement extends DialogElement { private final String description; + @Getter private final boolean newLine; + private final boolean required; private final boolean secret; private final boolean quiet; @@ -31,13 +33,13 @@ public class BaseQueryElement extends DialogElement { this.value = value; } - @Override - public boolean requiresExplicitUserInput() { - return required && value == null; - } - @Override public String toDisplayString() { return description; } + + @Override + public boolean requiresExplicitUserInput() { + return required && value == null; + } } diff --git a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java index 9f87a6585..2e37ad6bc 100644 --- a/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/ChoiceElement.java @@ -16,10 +16,13 @@ public class ChoiceElement extends DialogElement { @Getter private final String description; + @Getter private final List elements; + @Getter private final boolean required; + private final boolean quiet; @Getter @@ -39,13 +42,13 @@ public class ChoiceElement extends DialogElement { } @Override - public boolean requiresExplicitUserInput() { - return required && selected == -1; + public String toDisplayString() { + return description; } @Override - public String toDisplayString() { - return description; + public boolean requiresExplicitUserInput() { + return required && selected == -1; } @Override @@ -79,5 +82,4 @@ public class ChoiceElement extends DialogElement { return false; } - } diff --git a/core/src/main/java/io/xpipe/core/dialog/DialogElement.java b/core/src/main/java/io/xpipe/core/dialog/DialogElement.java index aca755820..97a643be8 100644 --- a/core/src/main/java/io/xpipe/core/dialog/DialogElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/DialogElement.java @@ -28,5 +28,4 @@ public abstract class DialogElement { public boolean apply(String value) { throw new UnsupportedOperationException(); } - } diff --git a/core/src/main/java/io/xpipe/core/dialog/HeaderElement.java b/core/src/main/java/io/xpipe/core/dialog/HeaderElement.java index 80870ead2..55e9e8fd0 100644 --- a/core/src/main/java/io/xpipe/core/dialog/HeaderElement.java +++ b/core/src/main/java/io/xpipe/core/dialog/HeaderElement.java @@ -28,5 +28,4 @@ public class HeaderElement extends DialogElement { public boolean apply(String value) { return true; } - } diff --git a/core/src/main/java/io/xpipe/core/process/CommandBuilder.java b/core/src/main/java/io/xpipe/core/process/CommandBuilder.java index b0ef9c29c..f9a446643 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandBuilder.java +++ b/core/src/main/java/io/xpipe/core/process/CommandBuilder.java @@ -10,6 +10,17 @@ import java.util.function.Function; public class CommandBuilder { + private final List elements = new ArrayList<>(); + @Getter + private final Map environmentVariables = new LinkedHashMap<>(); + private final List> setup = new ArrayList<>(); + @Getter + private CountDown countDown; + @Getter + private UUID uuid; + + private CommandBuilder() {} + public static CommandBuilder of() { return new CommandBuilder(); } @@ -22,18 +33,6 @@ public class CommandBuilder { return CommandBuilder.of().add(sc -> command.apply(sc)); } - private CommandBuilder() {} - - @Getter - private CountDown countDown; - @Getter - private UUID uuid; - - private final List elements = new ArrayList<>(); - @Getter - private final Map environmentVariables = new LinkedHashMap<>(); - private final List> setup = new ArrayList<>(); - public CommandBuilder setup(FailableConsumer consumer) { setup.add(consumer); return this; @@ -59,25 +58,6 @@ public class CommandBuilder { return this; } - public interface Element { - - String evaluate(ShellControl sc) throws Exception; - } - - static class Fixed implements Element { - - private final String string; - - Fixed(String string) { - this.string = string; - } - - @Override - public String evaluate(ShellControl sc) { - return string; - } - } - public CommandBuilder discardOutput() { elements.add(sc -> sc.getShellDialect().getDiscardOperator()); return this; @@ -104,7 +84,6 @@ public class CommandBuilder { return this; } - public CommandBuilder add(int index, String... s) { for (String s1 : s) { elements.add(index++, new Fixed(s1)); @@ -271,4 +250,23 @@ public class CommandBuilder { } return String.join(" ", list); } + + public interface Element { + + String evaluate(ShellControl sc) throws Exception; + } + + static class Fixed implements Element { + + private final String string; + + Fixed(String string) { + this.string = string; + } + + @Override + public String evaluate(ShellControl sc) { + return string; + } + } } diff --git a/core/src/main/java/io/xpipe/core/process/CommandControl.java b/core/src/main/java/io/xpipe/core/process/CommandControl.java index 41d2ff827..6f5460c76 100644 --- a/core/src/main/java/io/xpipe/core/process/CommandControl.java +++ b/core/src/main/java/io/xpipe/core/process/CommandControl.java @@ -21,15 +21,13 @@ public interface CommandControl extends ProcessControl { int INTERNAL_ERROR_EXIT_CODE = 163; int ELEVATION_FAILED_EXIT_CODE = 164; - enum TerminalExitMode { - KEEP_OPEN, - CLOSE - } - void setSensitive(); CommandControl withExceptionConverter(ExceptionConverter converter); + @Override + CommandControl start() throws Exception; + CommandControl withErrorFormatter(Function formatter); CommandControl terminalExitMode(TerminalExitMode mode); @@ -72,9 +70,6 @@ public interface CommandControl extends ProcessControl { CommandControl elevated(String message, FailableFunction elevationFunction); - @Override - CommandControl start() throws Exception; - void withStdoutOrThrow(FailableConsumer c); String readStdoutDiscardErr() throws Exception; @@ -113,4 +108,9 @@ public interface CommandControl extends ProcessControl { void discardOut(); void discardErr(); + + enum TerminalExitMode { + KEEP_OPEN, + CLOSE + } } diff --git a/core/src/main/java/io/xpipe/core/process/CountDown.java b/core/src/main/java/io/xpipe/core/process/CountDown.java index 255ec629f..c2a893a3e 100644 --- a/core/src/main/java/io/xpipe/core/process/CountDown.java +++ b/core/src/main/java/io/xpipe/core/process/CountDown.java @@ -5,10 +5,6 @@ import lombok.Setter; public class CountDown { - public static CountDown of() { - return new CountDown(); - } - private long lastMillis = -1; private long millisecondsLeft; @Setter @@ -16,7 +12,10 @@ public class CountDown { @Getter private long maxMillis; - private CountDown() { + private CountDown() {} + + public static CountDown of() { + return new CountDown(); } public synchronized void start(long millisecondsLeft) { @@ -24,7 +23,6 @@ public class CountDown { this.maxMillis = millisecondsLeft; lastMillis = System.currentTimeMillis(); active = true; - } public void pause() { diff --git a/core/src/main/java/io/xpipe/core/process/OsType.java b/core/src/main/java/io/xpipe/core/process/OsType.java index 823544d2b..30992b951 100644 --- a/core/src/main/java/io/xpipe/core/process/OsType.java +++ b/core/src/main/java/io/xpipe/core/process/OsType.java @@ -9,17 +9,6 @@ import java.util.stream.Collectors; public interface OsType { - sealed interface Local extends OsType permits OsType.Windows, OsType.Linux, OsType.MacOs { - - default Any toAny() { - return (Any) this; - } - } - - sealed interface Any extends OsType permits OsType.Windows, OsType.Linux, OsType.MacOs, OsType.Solaris, OsType.Bsd { - - } - Windows WINDOWS = new Windows(); Linux LINUX = new Linux(); MacOs MACOS = new MacOs(); @@ -53,6 +42,16 @@ public interface OsType { String determineOperatingSystemName(ShellControl pc) throws Exception; + sealed interface Local extends OsType permits OsType.Windows, OsType.Linux, OsType.MacOs { + + default Any toAny() { + return (Any) this; + } + } + + sealed interface Any extends OsType + permits OsType.Windows, OsType.Linux, OsType.MacOs, OsType.Solaris, OsType.Bsd {} + final class Windows implements OsType, Local, Any { @Override @@ -135,13 +134,13 @@ public interface OsType { } @Override - public String getTempDirectory(ShellControl pc) { - return "/tmp/"; + public String getName() { + return "Linux"; } @Override - public String getName() { - return "Linux"; + public String getTempDirectory(ShellControl pc) { + return "/tmp/"; } @Override @@ -171,8 +170,7 @@ public interface OsType { } } - - final class Linux extends Unix implements OsType, Local, Any { + final class Linux extends Unix implements OsType, Local, Any { @Override public String determineOperatingSystemName(ShellControl pc) throws Exception { @@ -194,15 +192,11 @@ public interface OsType { } } - final class Solaris extends Unix implements Any { + final class Solaris extends Unix implements Any {} - } + final class Bsd extends Unix implements Any {} - final class Bsd extends Unix implements Any { - - } - - final class MacOs implements OsType, Local, Any { + final class MacOs implements OsType, Local, Any { @Override public List determineInterestingPaths(ShellControl pc) throws Exception { @@ -223,6 +217,16 @@ public interface OsType { return pc.executeSimpleStringCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("HOME")); } + @Override + public String getFileSystemSeparator() { + return "/"; + } + + @Override + public String getName() { + return "Mac"; + } + @Override public String getTempDirectory(ShellControl pc) throws Exception { var found = pc.executeSimpleStringCommand(pc.getShellDialect().getPrintVariableCommand("TMPDIR")); @@ -235,20 +239,9 @@ public interface OsType { return found; } - @Override - public String getFileSystemSeparator() { - return "/"; - } - - @Override - public String getName() { - return "Mac"; - } - @Override public Map getProperties(ShellControl pc) throws Exception { - try (CommandControl c = - pc.command("sw_vers").start()) { + try (CommandControl c = pc.command("sw_vers").start()) { var text = c.readStdoutOrThrow(); return PropertiesFormatsParser.parse(text, ":"); } diff --git a/core/src/main/java/io/xpipe/core/process/ParentSystemAccess.java b/core/src/main/java/io/xpipe/core/process/ParentSystemAccess.java index dbd127341..a28108308 100644 --- a/core/src/main/java/io/xpipe/core/process/ParentSystemAccess.java +++ b/core/src/main/java/io/xpipe/core/process/ParentSystemAccess.java @@ -2,7 +2,6 @@ package io.xpipe.core.process; public interface ParentSystemAccess { - static ParentSystemAccess none() { return new ParentSystemAccess() { @Override @@ -35,11 +34,6 @@ public interface ParentSystemAccess { static ParentSystemAccess identity() { return new ParentSystemAccess() { - @Override - public boolean supportsExecutableEnvironment() { - return true; - } - @Override public boolean supportsFileSystemAccess() { return true; @@ -50,6 +44,11 @@ public interface ParentSystemAccess { return true; } + @Override + public boolean supportsExecutableEnvironment() { + return true; + } + @Override public String translateFromLocalSystemPath(String path) { return path; @@ -63,33 +62,33 @@ public interface ParentSystemAccess { } static ParentSystemAccess combine(ParentSystemAccess a1, ParentSystemAccess a2) { - return new ParentSystemAccess() { + return new ParentSystemAccess() { - @Override - public boolean supportsFileSystemAccess() { - return a1.supportsFileSystemAccess() && a2.supportsFileSystemAccess(); - } + @Override + public boolean supportsFileSystemAccess() { + return a1.supportsFileSystemAccess() && a2.supportsFileSystemAccess(); + } - @Override - public boolean supportsExecutables() { - return a1.supportsExecutables() && a2.supportsExecutables(); - } + @Override + public boolean supportsExecutables() { + return a1.supportsExecutables() && a2.supportsExecutables(); + } - @Override - public boolean supportsExecutableEnvironment() { - return a1.supportsExecutableEnvironment() && a2.supportsExecutableEnvironment(); - } + @Override + public boolean supportsExecutableEnvironment() { + return a1.supportsExecutableEnvironment() && a2.supportsExecutableEnvironment(); + } - @Override - public String translateFromLocalSystemPath(String path) throws Exception { - return a2.translateFromLocalSystemPath(a1.translateFromLocalSystemPath(path)); - } + @Override + public String translateFromLocalSystemPath(String path) throws Exception { + return a2.translateFromLocalSystemPath(a1.translateFromLocalSystemPath(path)); + } - @Override - public String translateToLocalSystemPath(String path) throws Exception { - return a1.translateToLocalSystemPath(a2.translateToLocalSystemPath(path)); - } - }; + @Override + public String translateToLocalSystemPath(String path) throws Exception { + return a1.translateToLocalSystemPath(a2.translateToLocalSystemPath(path)); + } + }; } default boolean supportsAnyAccess() { diff --git a/core/src/main/java/io/xpipe/core/process/ProcessControl.java b/core/src/main/java/io/xpipe/core/process/ProcessControl.java index 66c3d7bf3..d683fd08d 100644 --- a/core/src/main/java/io/xpipe/core/process/ProcessControl.java +++ b/core/src/main/java/io/xpipe/core/process/ProcessControl.java @@ -10,19 +10,15 @@ import java.util.UUID; public interface ProcessControl extends AutoCloseable { - @FunctionalInterface - interface ExceptionConverter { - T convert(T t); - } - UUID getUuid(); ProcessControl withExceptionConverter(ExceptionConverter converter); void resetData(boolean cache); - String prepareTerminalOpen(TerminalInitScriptConfig config, - FailableFunction workingDirectory) throws Exception; + String prepareTerminalOpen( + TerminalInitScriptConfig config, FailableFunction workingDirectory) + throws Exception; void closeStdin() throws IOException; @@ -52,4 +48,9 @@ public interface ProcessControl extends AutoCloseable { InputStream getStderr(); Charset getCharset(); + + @FunctionalInterface + interface ExceptionConverter { + T convert(T t); + } } diff --git a/core/src/main/java/io/xpipe/core/process/ProcessControlProvider.java b/core/src/main/java/io/xpipe/core/process/ProcessControlProvider.java index 00954d159..9e126d8d3 100644 --- a/core/src/main/java/io/xpipe/core/process/ProcessControlProvider.java +++ b/core/src/main/java/io/xpipe/core/process/ProcessControlProvider.java @@ -10,7 +10,9 @@ public abstract class ProcessControlProvider { public static void init(ModuleLayer layer) { INSTANCE = ServiceLoader.load(layer, ProcessControlProvider.class).stream() - .map(localProcessControlProviderProvider -> localProcessControlProviderProvider.get()).findFirst().orElseThrow(); + .map(localProcessControlProviderProvider -> localProcessControlProviderProvider.get()) + .findFirst() + .orElseThrow(); } public static ProcessControlProvider get() { @@ -20,14 +22,9 @@ public abstract class ProcessControlProvider { public abstract ShellControl withDefaultScripts(ShellControl pc); public abstract ShellControl sub( - ShellControl parent, - @NonNull ShellOpenFunction commandFunction, - ShellOpenFunction terminalCommand); + ShellControl parent, @NonNull ShellOpenFunction commandFunction, ShellOpenFunction terminalCommand); - public abstract CommandControl command( - ShellControl parent, - CommandBuilder command, - CommandBuilder terminalCommand); + public abstract CommandControl command(ShellControl parent, CommandBuilder command, CommandBuilder terminalCommand); public abstract ShellControl createLocalProcessControl(boolean stoppable); diff --git a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java index 828c5e2e1..171aa9271 100644 --- a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java +++ b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java @@ -8,9 +8,18 @@ import java.util.stream.Collectors; @Getter public class ProcessOutputException extends Exception { + private final long exitCode; + private final String output; + + private ProcessOutputException(String message, long exitCode, String output) { + super(message); + this.exitCode = exitCode; + this.output = output; + } + public static ProcessOutputException withParagraph(String customPrefix, ProcessOutputException ex) { var messageSuffix = ex.getOutput() != null ? ex.getOutput() : ""; - var message = customPrefix + "\n\n" + messageSuffix; + var message = customPrefix + "\n\n" + messageSuffix; return new ProcessOutputException(message, ex.getExitCode(), ex.getOutput()); } @@ -34,7 +43,9 @@ public class ProcessOutputException extends Exception { .START_FAILED_EXIT_CODE -> "Process did not start up properly and had to be killed" + errorSuffix; case CommandControl.EXIT_TIMEOUT_EXIT_CODE -> "Wait for process exit timed out" + errorSuffix; - case CommandControl.UNASSIGNED_EXIT_CODE -> "Process exited with unknown state. Did an external process interfere?" + errorSuffix; + case CommandControl + .UNASSIGNED_EXIT_CODE -> "Process exited with unknown state. Did an external process interfere?" + + errorSuffix; case CommandControl.INTERNAL_ERROR_EXIT_CODE -> "Process execution failed" + errorSuffix; case CommandControl.ELEVATION_FAILED_EXIT_CODE -> "Process elevation failed" + errorSuffix; default -> "Process returned exit code " + exitCode + errorSuffix; @@ -42,17 +53,12 @@ public class ProcessOutputException extends Exception { return new ProcessOutputException(message, exitCode, combinedError); } - private final long exitCode; - private final String output; - - private ProcessOutputException(String message, long exitCode, String output) { - super(message); - this.exitCode = exitCode; - this.output = output; - } - public boolean isIrregularExit() { - return exitCode == CommandControl.EXIT_TIMEOUT_EXIT_CODE || exitCode == CommandControl.START_FAILED_EXIT_CODE || exitCode == CommandControl.UNASSIGNED_EXIT_CODE || exitCode == CommandControl.INTERNAL_ERROR_EXIT_CODE || exitCode == CommandControl.ELEVATION_FAILED_EXIT_CODE; + return exitCode == CommandControl.EXIT_TIMEOUT_EXIT_CODE + || exitCode == CommandControl.START_FAILED_EXIT_CODE + || exitCode == CommandControl.UNASSIGNED_EXIT_CODE + || exitCode == CommandControl.INTERNAL_ERROR_EXIT_CODE + || exitCode == CommandControl.ELEVATION_FAILED_EXIT_CODE; } public boolean isKill() { diff --git a/core/src/main/java/io/xpipe/core/process/ScriptSnippet.java b/core/src/main/java/io/xpipe/core/process/ScriptSnippet.java index eb0affa25..ee2f6023b 100644 --- a/core/src/main/java/io/xpipe/core/process/ScriptSnippet.java +++ b/core/src/main/java/io/xpipe/core/process/ScriptSnippet.java @@ -5,6 +5,10 @@ import lombok.Getter; public interface ScriptSnippet { + String content(ShellControl shellControl); + + ExecutionType executionType(); + @Getter enum ExecutionType { @JsonProperty("dumbOnly") @@ -28,8 +32,4 @@ public interface ScriptSnippet { return this == TERMINAL_ONLY || this == BOTH; } } - - String content(ShellControl shellControl); - - ExecutionType executionType(); } diff --git a/core/src/main/java/io/xpipe/core/process/ShellControl.java b/core/src/main/java/io/xpipe/core/process/ShellControl.java index 525dd09a4..53c352c35 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellControl.java +++ b/core/src/main/java/io/xpipe/core/process/ShellControl.java @@ -18,8 +18,6 @@ public interface ShellControl extends ProcessControl { UUID getElevationSecretId(); - void setParentSystemAccess(ParentSystemAccess access); - List getExitUuids(); Optional getSourceStore(); @@ -30,6 +28,8 @@ public interface ShellControl extends ProcessControl { ParentSystemAccess getParentSystemAccess(); + void setParentSystemAccess(ParentSystemAccess access); + ParentSystemAccess getLocalSystemAccess(); boolean isLocal(); @@ -44,10 +44,10 @@ public interface ShellControl extends ProcessControl { ReentrantLock getLock(); - void setOriginalShellDialect(ShellDialect dialect); - ShellDialect getOriginalShellDialect(); + void setOriginalShellDialect(ShellDialect dialect); + ShellControl onInit(FailableConsumer pc); default ShellControl withShellStateInit(StatefulDataStore store) { @@ -75,9 +75,16 @@ public interface ShellControl extends ProcessControl { ShellControl withExceptionConverter(ExceptionConverter converter); + @Override + ShellControl start(); + ShellControl withErrorFormatter(Function formatter); - String prepareIntermediateTerminalOpen(String content, TerminalInitScriptConfig config, FailableFunction workingDirectory) throws Exception; + String prepareIntermediateTerminalOpen( + String content, + TerminalInitScriptConfig config, + FailableFunction workingDirectory) + throws Exception; String getSystemTemporaryDirectory(); @@ -146,7 +153,8 @@ public interface ShellControl extends ProcessControl { ShellSecurityPolicy getEffectiveSecurityPolicy(); - String buildElevatedCommand(CommandConfiguration input, String prefix, UUID requestId, CountDown countDown) throws Exception; + String buildElevatedCommand(CommandConfiguration input, String prefix, UUID requestId, CountDown countDown) + throws Exception; void restart() throws Exception; @@ -190,7 +198,8 @@ public interface ShellControl extends ProcessControl { return singularSubShell(o); } - default T enforceDialect(@NonNull ShellDialect type, FailableFunction sc) throws Exception { + default T enforceDialect(@NonNull ShellDialect type, FailableFunction sc) + throws Exception { if (isRunning() && getShellDialect().equals(type)) { return sc.apply(this); } else { @@ -200,8 +209,7 @@ public interface ShellControl extends ProcessControl { } } - ShellControl subShell( - ShellOpenFunction command, ShellOpenFunction terminalCommand); + ShellControl subShell(ShellOpenFunction command, ShellOpenFunction terminalCommand); ShellControl singularSubShell(ShellOpenFunction command); @@ -211,9 +219,6 @@ public interface ShellControl extends ProcessControl { void cd(String directory) throws Exception; - @Override - ShellControl start(); - default CommandControl command(String command) { return command(CommandBuilder.ofFunction(shellProcessControl -> command)); } diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialectAskpass.java b/core/src/main/java/io/xpipe/core/process/ShellDialectAskpass.java index 3ffdc0d30..70e7eb761 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialectAskpass.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialectAskpass.java @@ -9,7 +9,14 @@ public interface ShellDialectAskpass { String prepareFixedContent(ShellControl sc, String fileName, List s) throws Exception; - String elevateDumbCommand(ShellControl shellControl, CommandConfiguration command, UUID requestId, CountDown countDown, String message) throws Exception; + String elevateDumbCommand( + ShellControl shellControl, + CommandConfiguration command, + UUID requestId, + CountDown countDown, + String message) + throws Exception; - String elevateTerminalCommandWithPreparedAskpass(ShellControl shellControl, String command, String prefix) throws Exception; + String elevateTerminalCommandWithPreparedAskpass(ShellControl shellControl, String command, String prefix) + throws Exception; } diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialects.java b/core/src/main/java/io/xpipe/core/process/ShellDialects.java index 0e6984e50..5e4585ddc 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDialects.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDialects.java @@ -27,7 +27,28 @@ public class ShellDialects { public static ShellDialect RBASH; public static List getStartableDialects() { - return ALL.stream().filter(dialect -> dialect.getOpenCommand(null) != null).toList(); + return ALL.stream() + .filter(dialect -> dialect.getOpenCommand(null) != null) + .toList(); + } + + private static ShellDialect byId(String name) { + return ALL.stream() + .filter(shellType -> shellType.getId().equals(name)) + .findFirst() + .orElseThrow(); + } + + public static boolean isPowershell(ShellControl sc) { + return sc.getShellDialect().equals(POWERSHELL) || sc.getShellDialect().equals(POWERSHELL_CORE); + } + + public static ShellDialect byName(String name) { + return byNameIfPresent(name).orElseThrow(); + } + + public static Optional byNameIfPresent(String name) { + return ALL.stream().filter(shellType -> shellType.getId().equals(name)).findFirst(); } public static class Loader implements ModuleLayerLoader { @@ -64,25 +85,4 @@ public class ShellDialects { return true; } } - - private static ShellDialect byId(String name) { - return ALL.stream() - .filter(shellType -> shellType.getId().equals(name)) - .findFirst() - .orElseThrow(); - } - - public static boolean isPowershell(ShellControl sc) { - return sc.getShellDialect().equals(POWERSHELL) || sc.getShellDialect().equals(POWERSHELL_CORE); - } - - public static ShellDialect byName(String name) { - return byNameIfPresent(name).orElseThrow(); - } - - public static Optional byNameIfPresent(String name) { - return ALL.stream() - .filter(shellType -> shellType.getId().equals(name)) - .findFirst(); - } } diff --git a/core/src/main/java/io/xpipe/core/process/ShellDumbMode.java b/core/src/main/java/io/xpipe/core/process/ShellDumbMode.java index ce193c579..8acf0dee2 100644 --- a/core/src/main/java/io/xpipe/core/process/ShellDumbMode.java +++ b/core/src/main/java/io/xpipe/core/process/ShellDumbMode.java @@ -12,7 +12,8 @@ public interface ShellDumbMode { return null; } - default CommandBuilder prepareInlineDumbCommand(ShellControl self, ShellControl parent, ShellOpenFunction function) throws Exception { + default CommandBuilder prepareInlineDumbCommand(ShellControl self, ShellControl parent, ShellOpenFunction function) + throws Exception { return function.prepareWithoutInitCommand(); } @@ -31,7 +32,8 @@ public interface ShellDumbMode { } @Override - public CommandBuilder prepareInlineDumbCommand(ShellControl self, ShellControl parent, ShellOpenFunction function) throws Exception { + public CommandBuilder prepareInlineDumbCommand( + ShellControl self, ShellControl parent, ShellOpenFunction function) throws Exception { return function.prepareWithInitCommand(replacementDialect.getLoginOpenCommand(null)); } } @@ -43,6 +45,12 @@ public interface ShellDumbMode { return false; } + @Override + public CommandBuilder prepareInlineDumbCommand( + ShellControl self, ShellControl parent, ShellOpenFunction function) { + throw new UnsupportedOperationException(); + } + @Override public void prepareDumbInit(ShellControl shellControl) { throw new UnsupportedOperationException(); @@ -52,10 +60,5 @@ public interface ShellDumbMode { public void prepareDumbExit(ShellControl shellControl) { shellControl.kill(); } - - @Override - public CommandBuilder prepareInlineDumbCommand(ShellControl self, ShellControl parent, ShellOpenFunction function) { - throw new UnsupportedOperationException(); - } } } diff --git a/core/src/main/java/io/xpipe/core/process/SimpleScriptSnippet.java b/core/src/main/java/io/xpipe/core/process/SimpleScriptSnippet.java index 21472125c..b1122a5b1 100644 --- a/core/src/main/java/io/xpipe/core/process/SimpleScriptSnippet.java +++ b/core/src/main/java/io/xpipe/core/process/SimpleScriptSnippet.java @@ -6,6 +6,7 @@ public class SimpleScriptSnippet implements ScriptSnippet { @NonNull private final String content; + @NonNull private final ExecutionType executionType; diff --git a/core/src/main/java/io/xpipe/core/process/TerminalInitScriptConfig.java b/core/src/main/java/io/xpipe/core/process/TerminalInitScriptConfig.java index 0e8db9bb7..ce90b69fa 100644 --- a/core/src/main/java/io/xpipe/core/process/TerminalInitScriptConfig.java +++ b/core/src/main/java/io/xpipe/core/process/TerminalInitScriptConfig.java @@ -5,11 +5,11 @@ import lombok.Value; @Value public class TerminalInitScriptConfig { - public static TerminalInitScriptConfig ofName(String name) { - return new TerminalInitScriptConfig(name, true, false); - } - String displayName; boolean clearScreen; boolean hasColor; + + public static TerminalInitScriptConfig ofName(String name) { + return new TerminalInitScriptConfig(name, true, false); + } } diff --git a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java index 5931705d0..b44d4a076 100644 --- a/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/ConnectionFileSystem.java @@ -25,29 +25,6 @@ public class ConnectionFileSystem implements FileSystem { this.store = store; } - @Override - public List listRoots() throws Exception { - return shellControl.getShellDialect().listRoots(shellControl).toList(); - } - - @Override - public boolean directoryExists(String file) throws Exception { - return shellControl - .getShellDialect() - .directoryExists(shellControl, file) - .executeAndCheck(); - } - - @Override - public void directoryAccessible(String file) throws Exception { - shellControl.executeSimpleCommand(shellControl.getShellDialect().getCdCommand(file)); - } - - @Override - public Stream listFiles(String file) throws Exception { - return shellControl.getShellDialect().listFiles(this, shellControl, file); - } - @Override public FileSystemStore getStore() { return store; @@ -123,7 +100,8 @@ public class ConnectionFileSystem implements FileSystem { @Override public void mkdirs(String file) throws Exception { try (var pc = shellControl - .command(CommandBuilder.ofFunction(proc -> proc.getShellDialect().getMkdirsCommand(file))) + .command( + CommandBuilder.ofFunction(proc -> proc.getShellDialect().getMkdirsCommand(file))) .start()) { pc.discardOrThrow(); } @@ -149,6 +127,29 @@ public class ConnectionFileSystem implements FileSystem { } } + @Override + public boolean directoryExists(String file) throws Exception { + return shellControl + .getShellDialect() + .directoryExists(shellControl, file) + .executeAndCheck(); + } + + @Override + public void directoryAccessible(String file) throws Exception { + shellControl.executeSimpleCommand(shellControl.getShellDialect().getCdCommand(file)); + } + + @Override + public Stream listFiles(String file) throws Exception { + return shellControl.getShellDialect().listFiles(this, shellControl, file); + } + + @Override + public List listRoots() throws Exception { + return shellControl.getShellDialect().listRoots(shellControl).toList(); + } + @Override public void close() { // In case the shell control is already in an invalid state, this operation might fail diff --git a/core/src/main/java/io/xpipe/core/store/DataFlow.java b/core/src/main/java/io/xpipe/core/store/DataFlow.java index f55dd79d1..df7e86822 100644 --- a/core/src/main/java/io/xpipe/core/store/DataFlow.java +++ b/core/src/main/java/io/xpipe/core/store/DataFlow.java @@ -29,5 +29,4 @@ public enum DataFlow { public boolean hasOutput() { return this == OUTPUT || this == INPUT_OUTPUT; } - } diff --git a/core/src/main/java/io/xpipe/core/store/DataStoreId.java b/core/src/main/java/io/xpipe/core/store/DataStoreId.java index a717c3b84..6a60bd94a 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStoreId.java +++ b/core/src/main/java/io/xpipe/core/store/DataStoreId.java @@ -48,8 +48,7 @@ public class DataStoreId { } if (Arrays.stream(names).anyMatch(s -> s.contains("" + SEPARATOR))) { - throw new IllegalArgumentException( - "Separator character " + SEPARATOR + " is not allowed in the names"); + throw new IllegalArgumentException("Separator character " + SEPARATOR + " is not allowed in the names"); } if (Arrays.stream(names).anyMatch(s -> s.trim().length() == 0)) { @@ -73,7 +72,8 @@ public class DataStoreId { var split = s.split(String.valueOf(SEPARATOR), -1); - var names = Arrays.stream(split).map(String::trim).map(String::toLowerCase).toList(); + var names = + Arrays.stream(split).map(String::trim).map(String::toLowerCase).toList(); if (names.stream().anyMatch(s1 -> s1.isEmpty())) { throw new IllegalArgumentException("Name must not be empty"); } diff --git a/core/src/main/java/io/xpipe/core/store/DataStoreState.java b/core/src/main/java/io/xpipe/core/store/DataStoreState.java index 4f2bfc1aa..3a288f4b7 100644 --- a/core/src/main/java/io/xpipe/core/store/DataStoreState.java +++ b/core/src/main/java/io/xpipe/core/store/DataStoreState.java @@ -15,17 +15,17 @@ public abstract class DataStoreState { public abstract void merge(DataStoreState newer); - @SneakyThrows - public String toString() { - var tree = JacksonMapper.getDefault().valueToTree(this); - return tree.toPrettyString(); - } - @SneakyThrows public DataStoreState deepCopy() { return JacksonMapper.getDefault().treeToValue(JacksonMapper.getDefault().valueToTree(this), getClass()); } + @Override + public final int hashCode() { + var tree = JacksonMapper.getDefault().valueToTree(this); + return tree.hashCode(); + } + @Override public final boolean equals(Object o) { if (this == o) { @@ -40,9 +40,9 @@ public abstract class DataStoreState { return tree.equals(otherTree); } - @Override - public final int hashCode() { + @SneakyThrows + public String toString() { var tree = JacksonMapper.getDefault().valueToTree(this); - return tree.hashCode(); + return tree.toPrettyString(); } } diff --git a/core/src/main/java/io/xpipe/core/store/ExpandedLifecycleStore.java b/core/src/main/java/io/xpipe/core/store/ExpandedLifecycleStore.java index d3cba23d8..4ee263e9d 100644 --- a/core/src/main/java/io/xpipe/core/store/ExpandedLifecycleStore.java +++ b/core/src/main/java/io/xpipe/core/store/ExpandedLifecycleStore.java @@ -1,6 +1,6 @@ package io.xpipe.core.store; -public interface ExpandedLifecycleStore extends DataStore{ +public interface ExpandedLifecycleStore extends DataStore { default void initializeValidate() throws Exception {} diff --git a/core/src/main/java/io/xpipe/core/store/FileSystem.java b/core/src/main/java/io/xpipe/core/store/FileSystem.java index 3ca5ef4ef..da6fdcaab 100644 --- a/core/src/main/java/io/xpipe/core/store/FileSystem.java +++ b/core/src/main/java/io/xpipe/core/store/FileSystem.java @@ -17,85 +17,6 @@ import java.util.stream.Stream; public interface FileSystem extends Closeable, AutoCloseable { - @Value - @NonFinal - class FileEntry { - FileSystem fileSystem; - - @NonNull - @NonFinal - String path; - - @NonFinal - String extension; - - @NonFinal - String name; - - Instant date; - boolean hidden; - Boolean executable; - long size; - String mode; - - @NonNull - FileKind kind; - - public FileEntry( - FileSystem fileSystem, - @NonNull String path, - Instant date, - boolean hidden, - Boolean executable, - long size, - String mode, - @NonNull FileKind kind) { - this.fileSystem = fileSystem; - this.mode = mode; - this.kind = kind; - this.path = kind == FileKind.DIRECTORY ? FileNames.toDirectory(path) : path; - this.extension = FileNames.getExtension(path); - this.name = FileNames.getFileName(path); - this.date = date; - this.hidden = hidden; - this.executable = executable; - this.size = size; - } - - public void setPath(String path) { - this.path = path; - this.extension = FileNames.getExtension(path); - this.name = FileNames.getFileName(path); - } - - public FileEntry resolved() { - return this; - } - - public static FileEntry ofDirectory(FileSystem fileSystem, String path) { - return new FileEntry(fileSystem, path, Instant.now(), true, false, 0, null, FileKind.DIRECTORY); - } - } - - @Value - @EqualsAndHashCode(callSuper = true) - class LinkFileEntry extends FileEntry { - - @NonNull - FileEntry target; - - public LinkFileEntry( - @NonNull FileSystem fileSystem, @NonNull String path, Instant date, boolean hidden, Boolean executable, long size, String mode, @NonNull FileEntry target - ) { - super(fileSystem, path, date, hidden, executable, size, mode, FileKind.LINK); - this.target = target; - } - - public FileEntry resolved() { - return target; - } - } - FileSystemStore getStore(); Optional getShell(); @@ -150,4 +71,84 @@ public interface FileSystem extends Closeable, AutoCloseable { } List listRoots() throws Exception; + + @Value + @NonFinal + class FileEntry { + FileSystem fileSystem; + Instant date; + boolean hidden; + Boolean executable; + long size; + String mode; + @NonNull + FileKind kind; + @NonNull + @NonFinal + String path; + @NonFinal + String extension; + @NonFinal + String name; + + public FileEntry( + FileSystem fileSystem, + @NonNull String path, + Instant date, + boolean hidden, + Boolean executable, + long size, + String mode, + @NonNull FileKind kind) { + this.fileSystem = fileSystem; + this.mode = mode; + this.kind = kind; + this.path = kind == FileKind.DIRECTORY ? FileNames.toDirectory(path) : path; + this.extension = FileNames.getExtension(path); + this.name = FileNames.getFileName(path); + this.date = date; + this.hidden = hidden; + this.executable = executable; + this.size = size; + } + + public static FileEntry ofDirectory(FileSystem fileSystem, String path) { + return new FileEntry(fileSystem, path, Instant.now(), true, false, 0, null, FileKind.DIRECTORY); + } + + public void setPath(String path) { + this.path = path; + this.extension = FileNames.getExtension(path); + this.name = FileNames.getFileName(path); + } + + public FileEntry resolved() { + return this; + } + } + + @Value + @EqualsAndHashCode(callSuper = true) + class LinkFileEntry extends FileEntry { + + @NonNull + FileEntry target; + + public LinkFileEntry( + @NonNull FileSystem fileSystem, + @NonNull String path, + Instant date, + boolean hidden, + Boolean executable, + long size, + String mode, + @NonNull FileEntry target) { + super(fileSystem, path, date, hidden, executable, size, mode, FileKind.LINK); + this.target = target; + } + + public FileEntry resolved() { + return target; + } + } } diff --git a/core/src/main/java/io/xpipe/core/store/InputStreamStore.java b/core/src/main/java/io/xpipe/core/store/InputStreamStore.java index 285fec449..8c968ef52 100644 --- a/core/src/main/java/io/xpipe/core/store/InputStreamStore.java +++ b/core/src/main/java/io/xpipe/core/store/InputStreamStore.java @@ -1,8 +1,5 @@ package io.xpipe.core.store; -import io.xpipe.core.store.DataFlow; -import io.xpipe.core.store.StreamDataStore; - import java.io.InputStream; /** @@ -16,11 +13,6 @@ public class InputStreamStore implements StreamDataStore { this.in = in; } - @Override - public InputStream openInput() { - return in; - } - @Override public DataFlow getFlow() { return DataFlow.INPUT; @@ -30,4 +22,9 @@ public class InputStreamStore implements StreamDataStore { public boolean canOpen() { return true; } + + @Override + public InputStream openInput() { + return in; + } } \ No newline at end of file diff --git a/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java b/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java index 6a0284134..28d6a9d45 100644 --- a/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java +++ b/core/src/main/java/io/xpipe/core/store/OutputStreamStore.java @@ -12,13 +12,18 @@ public class OutputStreamStore implements StreamDataStore { } @Override - public boolean isContentExclusivelyAccessible() { - return true; + public DataFlow getFlow() { + return DataFlow.OUTPUT; } @Override - public DataFlow getFlow() { - return DataFlow.OUTPUT; + public boolean canOpen() { + return false; + } + + @Override + public boolean isContentExclusivelyAccessible() { + return true; } @Override @@ -30,9 +35,4 @@ public class OutputStreamStore implements StreamDataStore { public OutputStream openOutput() { return out; } - - @Override - public boolean canOpen() { - return false; - } } diff --git a/core/src/main/java/io/xpipe/core/store/StatefulDataStore.java b/core/src/main/java/io/xpipe/core/store/StatefulDataStore.java index 0ce256563..0896c21e7 100644 --- a/core/src/main/java/io/xpipe/core/store/StatefulDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StatefulDataStore.java @@ -20,23 +20,27 @@ public interface StatefulDataStore extends DataStore { @SuppressWarnings("unchecked") default T getState() { - return (T) DataStateProvider.get().getState(this, this::createDefaultState).deepCopy(); - } - - default T getState(Supplier def) { - return DataStateProvider.get().getState(this, def); + return (T) + DataStateProvider.get().getState(this, this::createDefaultState).deepCopy(); } default void setState(T val) { DataStateProvider.get().setState(this, val); } + default T getState(Supplier def) { + return DataStateProvider.get().getState(this, def); + } + @SneakyThrows @SuppressWarnings("unchecked") default Class getStateClass() { - var found = Arrays.stream(getClass().getDeclaredClasses()).filter(aClass -> DataStoreState.class.isAssignableFrom(aClass)).findAny(); + var found = Arrays.stream(getClass().getDeclaredClasses()) + .filter(aClass -> DataStoreState.class.isAssignableFrom(aClass)) + .findAny(); if (found.isEmpty()) { - throw new IllegalArgumentException("Store class " + getClass().getSimpleName() + " does not have a state class set"); + throw new IllegalArgumentException( + "Store class " + getClass().getSimpleName() + " does not have a state class set"); } return (Class) found.get(); diff --git a/core/src/main/java/io/xpipe/core/store/StdinDataStore.java b/core/src/main/java/io/xpipe/core/store/StdinDataStore.java index 5fea59209..4bbad9549 100644 --- a/core/src/main/java/io/xpipe/core/store/StdinDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StdinDataStore.java @@ -1,7 +1,6 @@ package io.xpipe.core.store; import com.fasterxml.jackson.annotation.JsonTypeName; -import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.util.JacksonizedValue; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; diff --git a/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java b/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java index 211d4f39c..c39f29207 100644 --- a/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java +++ b/core/src/main/java/io/xpipe/core/store/StdoutDataStore.java @@ -1,7 +1,6 @@ package io.xpipe.core.store; import com.fasterxml.jackson.annotation.JsonTypeName; -import io.xpipe.core.store.StreamDataStore; import io.xpipe.core.util.JacksonizedValue; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; diff --git a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java index 8877b47d9..fcb73e63e 100644 --- a/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java +++ b/core/src/main/java/io/xpipe/core/util/CoreJacksonModule.java @@ -151,7 +151,9 @@ public class CoreJacksonModule extends SimpleModule { public OsType.Local deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { var stream = Stream.of(OsType.WINDOWS, OsType.LINUX, OsType.MACOS); var n = p.getValueAsString(); - return stream.filter(osType -> osType.getName().equals(n)).findFirst().orElse(null); + return stream.filter(osType -> osType.getName().equals(n)) + .findFirst() + .orElse(null); } } @@ -161,7 +163,9 @@ public class CoreJacksonModule extends SimpleModule { public OsType.Any deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { var stream = Stream.of(OsType.WINDOWS, OsType.LINUX, OsType.BSD, OsType.SOLARIS, OsType.MACOS); var n = p.getValueAsString(); - return stream.filter(osType -> osType.getName().equals(n)).findFirst().orElse(null); + return stream.filter(osType -> osType.getName().equals(n)) + .findFirst() + .orElse(null); } } diff --git a/core/src/main/java/io/xpipe/core/util/InPlaceSecretValue.java b/core/src/main/java/io/xpipe/core/util/InPlaceSecretValue.java index 1c1e5228f..b083d9e53 100644 --- a/core/src/main/java/io/xpipe/core/util/InPlaceSecretValue.java +++ b/core/src/main/java/io/xpipe/core/util/InPlaceSecretValue.java @@ -15,6 +15,10 @@ import java.util.Random; @EqualsAndHashCode(callSuper = true) public class InPlaceSecretValue extends AesSecretValue { + public InPlaceSecretValue(char[] secret) { + super(secret); + } + public static InPlaceSecretValue of(String s) { return new InPlaceSecretValue(s.toCharArray()); } @@ -23,25 +27,11 @@ public class InPlaceSecretValue extends AesSecretValue { return new InPlaceSecretValue(c); } - public InPlaceSecretValue(char[] secret) { - super(secret); - } - @Override protected int getIterationCount() { return 2048; } - @Override - public InPlaceSecretValue inPlace() { - return this; - } - - @Override - public String toString() { - return ""; - } - protected byte[] getNonce(int numBytes) { byte[] nonce = new byte[numBytes]; new Random(1 - 28 + 213213).nextBytes(nonce); @@ -51,4 +41,14 @@ public class InPlaceSecretValue extends AesSecretValue { protected SecretKey getAESKey() throws InvalidKeySpecException { return getSecretKey(new char[] {'X', 'P', 'E' << 1}); } + + @Override + public InPlaceSecretValue inPlace() { + return this; + } + + @Override + public String toString() { + return ""; + } } diff --git a/core/src/main/java/io/xpipe/core/util/JacksonMapper.java b/core/src/main/java/io/xpipe/core/util/JacksonMapper.java index a6a72dc25..8699ffbd7 100644 --- a/core/src/main/java/io/xpipe/core/util/JacksonMapper.java +++ b/core/src/main/java/io/xpipe/core/util/JacksonMapper.java @@ -17,15 +17,10 @@ public class JacksonMapper { private static final ObjectMapper BASE = new ObjectMapper(); private static final ObjectMapper INSTANCE; + @Getter private static boolean init = false; - public static T parse(String s, Class c) throws JsonProcessingException { - var mapper = getDefault(); - var tree = mapper.readTree(s); - return mapper.treeToValue(tree, c); - } - static { ObjectMapper objectMapper = BASE; objectMapper.enable(SerializationFeature.INDENT_OUTPUT); @@ -44,6 +39,12 @@ public class JacksonMapper { INSTANCE = BASE.copy(); } + public static T parse(String s, Class c) throws JsonProcessingException { + var mapper = getDefault(); + var tree = mapper.readTree(s); + return mapper.treeToValue(tree, c); + } + public static synchronized void configure(Consumer mapper) { mapper.accept(INSTANCE); } @@ -86,5 +87,4 @@ public class JacksonMapper { return INSTANCE; } - } diff --git a/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java b/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java index d0e77db5a..8217420c2 100644 --- a/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java +++ b/core/src/main/java/io/xpipe/core/util/JacksonizedValue.java @@ -10,10 +10,10 @@ public class JacksonizedValue { public JacksonizedValue() {} - @SneakyThrows - public String toString() { + @Override + public final int hashCode() { var tree = JacksonMapper.getDefault().valueToTree(this); - return tree.toPrettyString(); + return tree.hashCode(); } @Override @@ -30,9 +30,9 @@ public class JacksonizedValue { return tree.equals(otherTree); } - @Override - public final int hashCode() { + @SneakyThrows + public String toString() { var tree = JacksonMapper.getDefault().valueToTree(this); - return tree.hashCode(); + return tree.toPrettyString(); } } diff --git a/core/src/main/java/io/xpipe/core/util/ProxyFunction.java b/core/src/main/java/io/xpipe/core/util/ProxyFunction.java index cb4e4f6d4..015f0c940 100644 --- a/core/src/main/java/io/xpipe/core/util/ProxyFunction.java +++ b/core/src/main/java/io/xpipe/core/util/ProxyFunction.java @@ -25,13 +25,13 @@ public abstract class ProxyFunction { @SneakyThrows public ProxyFunction callAndCopy() { -// var proxyStore = ProxyProvider.get().getProxy(getProxyBase()); -// if (proxyStore != null) { -// return ProxyProvider.get().call(this, proxyStore); -// } else { -// callLocal(); -// return this; -// } + // var proxyStore = ProxyProvider.get().getProxy(getProxyBase()); + // if (proxyStore != null) { + // return ProxyProvider.get().call(this, proxyStore); + // } else { + // callLocal(); + // return this; + // } return null; } diff --git a/core/src/main/java/io/xpipe/core/util/SecretReference.java b/core/src/main/java/io/xpipe/core/util/SecretReference.java index 2e0203dd3..281ca7172 100644 --- a/core/src/main/java/io/xpipe/core/util/SecretReference.java +++ b/core/src/main/java/io/xpipe/core/util/SecretReference.java @@ -9,13 +9,8 @@ import java.util.UUID; @AllArgsConstructor public class SecretReference { - public static SecretReference ofUuid(UUID secretId) { - return new SecretReference(secretId, 0); - } - UUID secretId; int subId; - public SecretReference(Object store) { this.secretId = UuidHelper.generateFromObject(store); this.subId = 0; @@ -25,4 +20,8 @@ public class SecretReference { this.secretId = UuidHelper.generateFromObject(store); this.subId = sub; } + + public static SecretReference ofUuid(UUID secretId) { + return new SecretReference(secretId, 0); + } } diff --git a/core/src/main/java/io/xpipe/core/util/SecretValue.java b/core/src/main/java/io/xpipe/core/util/SecretValue.java index 1559577d5..3e8c40932 100644 --- a/core/src/main/java/io/xpipe/core/util/SecretValue.java +++ b/core/src/main/java/io/xpipe/core/util/SecretValue.java @@ -10,8 +10,6 @@ import java.util.function.Function; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") public interface SecretValue { - InPlaceSecretValue inPlace(); - static String toBase64e(byte[] b) { var base64 = Base64.getEncoder().encodeToString(b); return base64.replace("/", "-"); @@ -21,6 +19,8 @@ public interface SecretValue { return Base64.getDecoder().decode(s.replace("-", "/")); } + InPlaceSecretValue inPlace(); + default void withSecretValue(Consumer con) { var chars = getSecret(); con.accept(chars); diff --git a/core/src/main/java/io/xpipe/core/util/XPipeDaemonMode.java b/core/src/main/java/io/xpipe/core/util/XPipeDaemonMode.java index 5b7a32168..0085930b5 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeDaemonMode.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeDaemonMode.java @@ -19,6 +19,14 @@ public enum XPipeDaemonMode { @JsonProperty("gui") GUI("gui", List.of("gui", "desktop", "interface")); + private final String displayName; + private final List nameAlternatives; + + XPipeDaemonMode(String displayName, List nameAlternatives) { + this.displayName = displayName; + this.nameAlternatives = nameAlternatives; + } + public static XPipeDaemonMode get(String name) { return Arrays.stream(XPipeDaemonMode.values()) .filter(xPipeDaemonMode -> @@ -29,13 +37,4 @@ public enum XPipeDaemonMode { .map(XPipeDaemonMode::getDisplayName) .collect(Collectors.joining(", ")))); } - - private final String displayName; - - private final List nameAlternatives; - - XPipeDaemonMode(String displayName, List nameAlternatives) { - this.displayName = displayName; - this.nameAlternatives = nameAlternatives; - } } diff --git a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java index 52f365924..1ba4d50be 100644 --- a/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java +++ b/core/src/main/java/io/xpipe/core/util/XPipeInstallation.java @@ -57,8 +57,7 @@ public class XPipeInstallation { var suffix = (arguments != null ? " " + arguments : ""); var modeOption = mode != null ? " --mode " + mode.getDisplayName() : ""; if (OsType.getLocal().equals(OsType.LINUX)) { - return "nohup \"" + installationBase + "/app/bin/xpiped\"" + modeOption + suffix - + " & disown"; + return "nohup \"" + installationBase + "/app/bin/xpiped\"" + modeOption + suffix + " & disown"; } else if (OsType.getLocal().equals(OsType.MACOS)) { return "open \"" + installationBase + "\" --args" + modeOption + suffix; } @@ -77,7 +76,8 @@ public class XPipeInstallation { public static Path getCurrentInstallationBasePath() { // We should always have a command associated with the current process, otherwise something went seriously wrong // Resolve any possible links to a real path - Path path = toRealPathIfPossible(Path.of(ProcessHandle.current().info().command().orElseThrow())); + Path path = toRealPathIfPossible( + Path.of(ProcessHandle.current().info().command().orElseThrow())); // Check if the process was started using a relative path, and adapt it if necessary if (!path.isAbsolute()) { path = toRealPathIfPossible(Path.of(System.getProperty("user.dir")).resolve(path)); @@ -86,7 +86,8 @@ public class XPipeInstallation { var name = path.getFileName().toString(); // Check if we launched the JVM via a start script instead of the native executable if (name.endsWith("java") || name.endsWith("java.exe")) { - // If we are not an image, we are probably running in a development environment where we want to use the working directory + // If we are not an image, we are probably running in a development environment where we want to use the + // working directory var isImage = ModuleHelper.isImage(); if (!isImage) { return Path.of(System.getProperty("user.dir")); @@ -211,7 +212,8 @@ public class XPipeInstallation { } public static String queryInstallationVersion(ShellControl p, String exec) throws Exception { - try (CommandControl c = p.command(CommandBuilder.of().addFile(exec).add("version")).start()) { + try (CommandControl c = + p.command(CommandBuilder.of().addFile(exec).add("version")).start()) { return c.readStdoutOrThrow(); } catch (ProcessOutputException ex) { return "?"; diff --git a/ext/base/src/localTest/java/test/Test.java b/ext/base/src/localTest/java/test/Test.java index c11a10b62..2e75f5fb3 100644 --- a/ext/base/src/localTest/java/test/Test.java +++ b/ext/base/src/localTest/java/test/Test.java @@ -9,6 +9,5 @@ public class Test extends LocalExtensionTest { public void test() { System.out.println("a"); System.out.println(DataStorage.get().getStoreEntries()); - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/SelfReferentialStore.java b/ext/base/src/main/java/io/xpipe/ext/base/SelfReferentialStore.java index 3c5386092..9baa89362 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/SelfReferentialStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/SelfReferentialStore.java @@ -9,10 +9,14 @@ import java.util.UUID; public interface SelfReferentialStore extends DataStore { default DataStoreEntry getSelfEntry() { - return DataStorage.get().getStoreEntryIfPresent(this).or(() -> { - return DataStorage.get().getStoreEntryInProgressIfPresent(this); - }).orElseGet(() -> { - return DataStoreEntry.createNew(UUID.randomUUID(),DataStorage.DEFAULT_CATEGORY_UUID, "Invalid", this); - }); + return DataStorage.get() + .getStoreEntryIfPresent(this) + .or(() -> { + return DataStorage.get().getStoreEntryInProgressIfPresent(this); + }) + .orElseGet(() -> { + return DataStoreEntry.createNew( + UUID.randomUUID(), DataStorage.DEFAULT_CATEGORY_UUID, "Invalid", this); + }); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java index 51c52b082..aec80275b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/BrowseStoreAction.java @@ -13,27 +13,20 @@ import lombok.Value; public class BrowseStoreAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry entry; - - @Override - public boolean requiresJavaFXPlatform() { - return true; - } - - @Override - public void execute() { - BrowserModel.DEFAULT.openFileSystemAsync(entry.ref(),null, new SimpleBooleanProperty()); - AppLayoutModel.get().selectBrowser(); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite() { + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return ShellStore.class; + } + @Override public boolean isMajor(DataStoreEntryRef o) { return true; @@ -48,16 +41,23 @@ public class BrowseStoreAction implements ActionProvider { public String getIcon(DataStoreEntryRef store) { return "mdi2f-folder-open-outline"; } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return ShellStore.class; - } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry entry; + + @Override + public boolean requiresJavaFXPlatform() { + return true; + } + + @Override + public void execute() { + BrowserModel.DEFAULT.openFileSystemAsync(entry.ref(), null, new SimpleBooleanProperty()); + AppLayoutModel.get().selectBrowser(); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/CloneStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/CloneStoreAction.java index 7eda77fbc..509761bbb 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/CloneStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/CloneStoreAction.java @@ -11,23 +11,6 @@ import lombok.Value; public class CloneStoreAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry store; - - @Override - public boolean requiresJavaFXPlatform() { - return false; - } - - @Override - public void execute() { - DataStorage.get().addStoreEntryIfNotPresent( - DataStoreEntry.createNew(store.getName() + " (Copy)",store.getStore())); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite<>() { @@ -63,4 +46,21 @@ public class CloneStoreAction implements ActionProvider { } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public boolean requiresJavaFXPlatform() { + return false; + } + + @Override + public void execute() { + DataStorage.get() + .addStoreEntryIfNotPresent(DataStoreEntry.createNew(store.getName() + " (Copy)", store.getStore())); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java index ad9fd1cdc..f57fbf488 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/DeleteStoreChildrenAction.java @@ -5,29 +5,13 @@ import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntryRef; -import io.xpipe.core.store.DataStore; import io.xpipe.app.util.FixedHierarchyStore; +import io.xpipe.core.store.DataStore; import javafx.beans.value.ObservableValue; import lombok.Value; public class DeleteStoreChildrenAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry store; - - @Override - public boolean requiresJavaFXPlatform() { - return false; - } - - @Override - public void execute() { - DataStorage.get().deleteChildren(store); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite<>() { @@ -49,10 +33,8 @@ public class DeleteStoreChildrenAction implements ActionProvider { @Override public boolean isApplicable(DataStoreEntryRef o) { - return !(o.getStore() instanceof FixedHierarchyStore) && DataStorage.get() - .getStoreChildren(o.get()) - .size() - > 1; + return !(o.getStore() instanceof FixedHierarchyStore) + && DataStorage.get().getStoreChildren(o.get()).size() > 1; } @Override @@ -66,4 +48,20 @@ public class DeleteStoreChildrenAction implements ActionProvider { } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public boolean requiresJavaFXPlatform() { + return false; + } + + @Override + public void execute() { + DataStorage.get().deleteChildren(store); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/EditStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/EditStoreAction.java index 516a548c4..0e0aa7254 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/EditStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/EditStoreAction.java @@ -11,64 +11,10 @@ import lombok.Value; public class EditStoreAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry store; - - @Override - public boolean requiresJavaFXPlatform() { - return true; - } - - @Override - public void execute() { - StoreCreationComp.showEdit(store); - } - } - - @Override - public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { - return new DefaultDataStoreCallSite<>() { - @Override - public boolean isApplicable(DataStoreEntryRef o) { - if (o.get() - .getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { - return false; - } - - return o.get() - .getValidity() - .equals(DataStoreEntry.Validity.INCOMPLETE) || o.get().getProvider().editByDefault(); - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return DataStore.class; - } - }; - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite<>() { - @Override - public boolean isMajor(DataStoreEntryRef o) { - var provider = o.get().getProvider(); - return provider.shouldEdit(); - } - - @Override - public ActiveType activeType() { - return ActiveType.ALWAYS_ENABLE; - } - @Override public boolean isSystemAction() { return true; @@ -84,6 +30,12 @@ public class EditStoreAction implements ActionProvider { return DataStore.class; } + @Override + public boolean isMajor(DataStoreEntryRef o) { + var provider = o.get().getProvider(); + return provider.shouldEdit(); + } + @Override public ObservableValue getName(DataStoreEntryRef store) { return AppI18n.observable("base.edit"); @@ -93,6 +45,52 @@ public class EditStoreAction implements ActionProvider { public String getIcon(DataStoreEntryRef store) { return "mdal-edit"; } + + @Override + public ActiveType activeType() { + return ActiveType.ALWAYS_ENABLE; + } }; } + + @Override + public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { + return new DefaultDataStoreCallSite<>() { + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return DataStore.class; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + if (o.get().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) { + return false; + } + + return o.get().getValidity().equals(DataStoreEntry.Validity.INCOMPLETE) + || o.get().getProvider().editByDefault(); + } + }; + } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + @Override + public boolean requiresJavaFXPlatform() { + return true; + } + + @Override + public void execute() { + StoreCreationComp.showEdit(store); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java index d1e357b9f..874e2f9cb 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchAction.java @@ -14,6 +14,68 @@ import lombok.Value; public class LaunchAction implements ActionProvider { + @Override + public String getId() { + return "launch"; + } + + @Override + public DataStoreCallSite getDataStoreCallSite() { + return new DataStoreCallSite() { + + @Override + public boolean canLinkTo() { + return true; + } + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return LaunchableStore.class; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + return o.get().getValidity().isUsable() && o.getStore().canLaunch(); + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("launch"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2p-play"; + } + }; + } + + @Override + public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { + return new DefaultDataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return LaunchableStore.class; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + return o.get().getValidity().isUsable() && o.getStore().canLaunch(); + } + }; + } + @Value static class Action implements ActionProvider.Action { @@ -42,70 +104,4 @@ public class LaunchAction implements ActionProvider { } } } - - @Override - public DataStoreCallSite getDataStoreCallSite() { - return new DataStoreCallSite() { - - @Override - public boolean canLinkTo() { - return true; - } - - @Override - public boolean isApplicable(DataStoreEntryRef o) { - return o.get() - .getValidity() - .isUsable() && o.getStore().canLaunch(); - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return LaunchableStore.class; - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - return AppI18n.observable("launch"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - return "mdi2p-play"; - } - }; - } - - @Override - public String getId() { - return "launch"; - } - - @Override - public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { - return new DefaultDataStoreCallSite() { - - @Override - public boolean isApplicable(DataStoreEntryRef o) { - return o.get() - .getValidity() - .isUsable() && o.getStore().canLaunch(); - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return LaunchableStore.class; - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java index b3ee56a10..a0a7a27ba 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/LaunchShortcutAction.java @@ -11,22 +11,6 @@ import lombok.Value; public class LaunchShortcutAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry entry; - - @Override - public boolean requiresJavaFXPlatform() { - return false; - } - - @Override - public void execute() throws Exception { - DesktopShortcuts.create("xpipe://launch/" + entry.getUuid().toString(), entry.getName()); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite() { @@ -50,7 +34,22 @@ public class LaunchShortcutAction implements ActionProvider { public String getIcon(DataStoreEntryRef store) { return "mdi2c-code-greater-than"; } - }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry entry; + + @Override + public boolean requiresJavaFXPlatform() { + return false; + } + + @Override + public void execute() throws Exception { + DesktopShortcuts.create("xpipe://launch/" + entry.getUuid().toString(), entry.getName()); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/ObserveStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/ObserveStoreAction.java index fa76a7e00..85212dfa0 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/ObserveStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/ObserveStoreAction.java @@ -9,22 +9,6 @@ import lombok.Value; public class ObserveStoreAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntryRef store; - - @Override - public boolean requiresJavaFXPlatform() { - return true; - } - - @Override - public void execute() { - store.getStore().toggleObserverState(!store.getStore().getObserverState()); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite() { @@ -41,7 +25,9 @@ public class ObserveStoreAction implements ActionProvider { @Override public ObservableValue getName(DataStoreEntryRef store) { - return store.getStore().getObserverState() ? AppI18n.observable("base.stopObserve") : AppI18n.observable("base.observe"); + return store.getStore().getObserverState() + ? AppI18n.observable("base.stopObserve") + : AppI18n.observable("base.observe"); } @Override @@ -50,4 +36,20 @@ public class ObserveStoreAction implements ActionProvider { } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntryRef store; + + @Override + public boolean requiresJavaFXPlatform() { + return true; + } + + @Override + public void execute() { + store.getStore().toggleObserverState(!store.getStore().getObserverState()); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java index f07636804..7da0c0663 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/RefreshStoreAction.java @@ -9,7 +9,59 @@ import io.xpipe.app.util.FixedHierarchyStore; import javafx.beans.value.ObservableValue; import lombok.Value; -public class RefreshStoreAction implements ActionProvider { +public class RefreshStoreAction implements ActionProvider { + + @Override + public ActionProvider.DataStoreCallSite getDataStoreCallSite() { + return new ActionProvider.DataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return FixedHierarchyStore.class; + } + + @Override + public boolean isMajor(DataStoreEntryRef o) { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("base.refresh"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2r-refresh"; + } + }; + } + + @Override + public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { + return new DefaultDataStoreCallSite<>() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return FixedHierarchyStore.class; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + return DataStorage.get().getStoreChildren(o.get()).size() == 0; + } + }; + } @Value static class Action implements ActionProvider.Action { @@ -26,56 +78,4 @@ public class RefreshStoreAction implements ActionProvider { DataStorage.get().refreshChildren(store); } } - - @Override - public DefaultDataStoreCallSite getDefaultDataStoreCallSite() { - return new DefaultDataStoreCallSite<>() { - - @Override - public boolean isApplicable(DataStoreEntryRef o) { - return DataStorage.get().getStoreChildren(o.get()).size() == 0; - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return FixedHierarchyStore.class; - } - }; - } - - @Override - public ActionProvider.DataStoreCallSite getDataStoreCallSite() { - return new ActionProvider.DataStoreCallSite() { - - @Override - public boolean isMajor(DataStoreEntryRef o) { - return true; - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return FixedHierarchyStore.class; - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - return AppI18n.observable("base.refresh"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - return "mdi2r-refresh"; - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/SampleAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/SampleAction.java index 75c27d98d..9880669fd 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/SampleAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/SampleAction.java @@ -17,6 +17,44 @@ import java.io.InputStreamReader; public class SampleAction implements ActionProvider { + @Override + public DataStoreCallSite getDataStoreCallSite() { + // Call sites represent different ways of invoking the action. + // In this case, this represents a button that is shown for all stored shell connections. + return new DataStoreCallSite() { + + @Override + public Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + // For which general type of connection store to make this action available. + return ShellStore.class; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + // Allows you to individually check whether this action should be available for the specific store. + // In this case it should only be available for remote shell connections, not local ones. + return !ShellStore.isLocal(o.getStore()); + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + // The displayed name of the action, allows you to use translation keys. + return AppI18n.observable("installConnector"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + // The ikonli icon of the button. + return "mdi2c-code-greater-than"; + } + }; + } + @Value static class Action implements ActionProvider.Action { @@ -105,42 +143,4 @@ public class SampleAction implements ActionProvider { } } } - - @Override - public DataStoreCallSite getDataStoreCallSite() { - // Call sites represent different ways of invoking the action. - // In this case, this represents a button that is shown for all stored shell connections. - return new DataStoreCallSite() { - - @Override - public Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - // For which general type of connection store to make this action available. - return ShellStore.class; - } - - @Override - public boolean isApplicable(DataStoreEntryRef o) { - // Allows you to individually check whether this action should be available for the specific store. - // In this case it should only be available for remote shell connections, not local ones. - return !ShellStore.isLocal(o.getStore()); - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - // The displayed name of the action, allows you to use translation keys. - return AppI18n.observable("installConnector"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - // The ikonli icon of the button. - return "mdi2c-code-greater-than"; - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java index c12d46498..9ba46c34c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/ScanAction.java @@ -11,26 +11,20 @@ import lombok.Value; public class ScanAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry entry; - - @Override - public boolean requiresJavaFXPlatform() { - return true; - } - - @Override - public void execute() { - ScanAlert.showAsync(entry); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite() { + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store.get()); + } + + @Override + public Class getApplicableClass() { + return ShellStore.class; + } + @Override public boolean isMajor(DataStoreEntryRef o) { return o.get().getProvider().shouldHaveChildren(); @@ -50,16 +44,22 @@ public class ScanAction implements ActionProvider { public String getIcon(DataStoreEntryRef store) { return "mdi2m-magnify-scan"; } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store.get()); - } - - @Override - public Class getApplicableClass() { - return ShellStore.class; - } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry entry; + + @Override + public boolean requiresJavaFXPlatform() { + return true; + } + + @Override + public void execute() { + ScanAlert.showAsync(entry); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/ShareStoreAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/ShareStoreAction.java index 5e2e9951e..61420e415 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/ShareStoreAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/ShareStoreAction.java @@ -13,29 +13,6 @@ import lombok.Value; public class ShareStoreAction implements ActionProvider { - @Value - static class Action implements ActionProvider.Action { - - DataStoreEntry store; - - @Override - public boolean requiresJavaFXPlatform() { - return false; - } - - public static String create(DataStore store) { - return "xpipe://addStore/" - + InPlaceSecretValue.of(store.toString()).getEncryptedValue(); - } - - @Override - public void execute() { - var url = create(store.getStore()); - AppActionLinkDetector.setLastDetectedAction(url); - ClipboardHelper.copyUrl(url); - } - } - @Override public DataStoreCallSite getDataStoreCallSite() { return new DataStoreCallSite<>() { @@ -66,4 +43,26 @@ public class ShareStoreAction implements ActionProvider { } }; } + + @Value + static class Action implements ActionProvider.Action { + + DataStoreEntry store; + + public static String create(DataStore store) { + return "xpipe://addStore/" + InPlaceSecretValue.of(store.toString()).getEncryptedValue(); + } + + @Override + public boolean requiresJavaFXPlatform() { + return false; + } + + @Override + public void execute() { + var url = create(store.getStore()); + AppActionLinkDetector.setLastDetectedAction(url); + ClipboardHelper.copyUrl(url); + } + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java b/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java index 36d777781..c32c2eb99 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/action/XPipeUrlAction.java @@ -1,14 +1,14 @@ package io.xpipe.ext.base.action; -import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.comp.store.StoreCreationComp; +import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.ext.ActionProvider; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.util.TerminalLauncher; +import io.xpipe.core.store.DataStore; import io.xpipe.core.store.LaunchableStore; import io.xpipe.core.util.InPlaceSecretValue; -import io.xpipe.core.store.DataStore; import io.xpipe.core.util.JacksonMapper; import lombok.Value; @@ -17,6 +17,59 @@ import java.util.UUID; public class XPipeUrlAction implements ActionProvider { + @Override + public LauncherCallSite getLauncherCallSite() { + return new XPipeLauncherCallSite() { + + @Override + public String getId() { + return "xpipe"; + } + + @Override + public Action createAction(List args) throws Exception { + switch (args.get(0)) { + case "addStore" -> { + var storeString = InPlaceSecretValue.builder() + .encryptedValue(args.get(1)) + .build(); + var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class); + return new AddStoreAction(store); + } + case "launch" -> { + var entry = DataStorage.get() + .getStoreEntryIfPresent(UUID.fromString(args.get(1))) + .orElseThrow(); + if (!entry.getValidity().isUsable()) { + return null; + } + return new LaunchAction(entry); + } + case "action" -> { + var id = args.get(1); + ActionProvider provider = ActionProvider.ALL.stream() + .filter(actionProvider -> { + return actionProvider.getDataStoreCallSite() != null + && id.equals(actionProvider.getId()); + }) + .findFirst() + .orElseThrow(); + var entry = DataStorage.get() + .getStoreEntryIfPresent(UUID.fromString(args.get(2))) + .orElseThrow(); + if (!entry.getValidity().isUsable()) { + return null; + } + return new CallAction(provider, entry); + } + default -> { + return null; + } + } + } + }; + } + @Value static class CallAction implements ActionProvider.Action { @@ -74,57 +127,16 @@ public class XPipeUrlAction implements ActionProvider { return; } - var entry = DataStoreEntry.createNew(UUID.randomUUID(), StoreViewState.get().getActiveCategory().getValue().getCategory().getUuid(), "", store); + var entry = DataStoreEntry.createNew( + UUID.randomUUID(), + StoreViewState.get() + .getActiveCategory() + .getValue() + .getCategory() + .getUuid(), + "", + store); StoreCreationComp.showEdit(entry); } } - - @Override - public LauncherCallSite getLauncherCallSite() { - return new XPipeLauncherCallSite() { - - @Override - public String getId() { - return "xpipe"; - } - - @Override - public Action createAction(List args) throws Exception { - switch (args.get(0)) { - case "addStore" -> { - var storeString = InPlaceSecretValue.builder() - .encryptedValue(args.get(1)) - .build(); - var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class); - return new AddStoreAction(store); - } - case "launch" -> { - var entry = DataStorage.get() - .getStoreEntryIfPresent(UUID.fromString(args.get(1))) - .orElseThrow(); - if (!entry.getValidity().isUsable()) { - return null; - } - return new LaunchAction(entry); - } - case "action" -> { - var id = args.get(1); - ActionProvider provider = ActionProvider.ALL.stream().filter(actionProvider -> { - return actionProvider.getDataStoreCallSite() != null && id.equals(actionProvider.getId()); - }).findFirst().orElseThrow(); - var entry = DataStorage.get() - .getStoreEntryIfPresent(UUID.fromString(args.get(2))) - .orElseThrow(); - if (!entry.getValidity().isUsable()) { - return null; - } - return new CallAction(provider, entry); - } - default -> { - return null; - } - } - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java index 10c0e17d0..1a3c877a3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/BackAction.java @@ -13,30 +13,20 @@ import java.util.List; public class BackAction implements LeafAction { - public String getId() { - return "back"; - } - @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { model.backSync(1); } + public String getId() { + return "back"; + } + @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("fth-arrow-left"); } - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return false; - } - - @Override - public boolean isActive(OpenFileSystemModel model, List entries) { - return model.getHistory().canGoBackProperty().get(); - } - @Override public KeyCombination getShortcut() { return new KeyCodeCombination(KeyCode.LEFT, KeyCombination.ALT_DOWN); @@ -46,4 +36,14 @@ public class BackAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Back"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return false; + } + + @Override + public boolean isActive(OpenFileSystemModel model, List entries) { + return model.getHistory().canGoBackProperty().get(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java index 0c6d38e49..5dd23c056 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/BrowseInNativeManagerAction.java @@ -57,11 +57,6 @@ public class BrowseInNativeManagerAction implements LeafAction { return true; } - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return model.getFileSystem().getShell().orElseThrow().getLocalSystemAccess().supportsFileSystemAccess(); - } - @Override public String getName(OpenFileSystemModel model, List entries) { return switch (OsType.getLocal()) { @@ -70,4 +65,13 @@ public class BrowseInNativeManagerAction implements LeafAction { case OsType.MacOs macOs -> "Browse in Finder"; }; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return model.getFileSystem() + .getShell() + .orElseThrow() + .getLocalSystemAccess() + .supportsFileSystemAccess(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java index 5db59f4b9..b64f5115e 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/ChmodAction.java @@ -14,8 +14,8 @@ import java.util.List; public class ChmodAction implements BranchAction { @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS; + public Node getIcon(OpenFileSystemModel model, List entries) { + return new FontIcon("mdi2w-wrench"); } @Override @@ -24,35 +24,52 @@ public class ChmodAction implements BranchAction { } @Override - public Node getIcon(OpenFileSystemModel model, List entries) { - return new FontIcon("mdi2w-wrench"); + public String getName(OpenFileSystemModel model, List entries) { + return "Chmod"; } @Override - public String getName(OpenFileSystemModel model, List entries) { - return "Chmod"; + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS; + } + + @Override + public List getBranchingActions(OpenFileSystemModel model, List entries) { + return List.of( + new Chmod("400"), + new Chmod("600"), + new Chmod("644"), + new Chmod("700"), + new Chmod("755"), + new Chmod("777"), + new Chmod("u+x"), + new Chmod("a+x")); } private static class Chmod implements LeafAction { private final String option; - private Chmod(String option) {this.option = option;} + private Chmod(String option) { + this.option = option; + } @Override public String getName(OpenFileSystemModel model, List entries) { return option; - } + } @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { - model.getFileSystem().getShell().orElseThrow().executeSimpleCommand(CommandBuilder.of().add("chmod", option) - .addFiles(entries.stream().map(browserEntry -> browserEntry.getRawFileEntry().getPath()).toList())); + model.getFileSystem() + .getShell() + .orElseThrow() + .executeSimpleCommand(CommandBuilder.of() + .add("chmod", option) + .addFiles(entries.stream() + .map(browserEntry -> + browserEntry.getRawFileEntry().getPath()) + .toList())); } } - - @Override - public List getBranchingActions(OpenFileSystemModel model, List entries) { - return List.of(new Chmod("400"), new Chmod("600"), new Chmod("644"), new Chmod("700"), new Chmod("755"), new Chmod("777"), new Chmod("u+x"), new Chmod("a+x")); - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java index 830bd46ba..084f1fac5 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyAction.java @@ -21,11 +21,6 @@ public class CopyAction implements LeafAction { entries.stream().map(entry -> entry.getRawFileEntry()).toList()); } - @Override - public boolean acceptsEmptySelection() { - return true; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2c-content-copy"); @@ -41,6 +36,11 @@ public class CopyAction implements LeafAction { return new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN); } + @Override + public boolean acceptsEmptySelection() { + return true; + } + @Override public String getName(OpenFileSystemModel model, List entries) { return "Copy"; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java index 3079d6298..9f866a43b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/CopyPathAction.java @@ -18,11 +18,6 @@ import java.util.stream.Collectors; public class CopyPathAction implements BrowserAction, BranchAction { - @Override - public String getName(OpenFileSystemModel model, List entries) { - return "Copy location"; - } - @Override public Category getCategory() { return Category.COPY_PASTE; @@ -33,10 +28,20 @@ public class CopyPathAction implements BrowserAction, BranchAction { return true; } + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "Copy location"; + } + @Override public List getBranchingActions(OpenFileSystemModel model, List entries) { return List.of( new LeafAction() { + @Override + public KeyCombination getShortcut() { + return new KeyCodeCombination(KeyCode.C, KeyCombination.ALT_DOWN, KeyCombination.SHORTCUT_DOWN); + } + @Override public String getName(OpenFileSystemModel model, List entries) { if (entries.size() == 1) { @@ -48,11 +53,6 @@ public class CopyPathAction implements BrowserAction, BranchAction { return "Absolute Paths"; } - @Override - public KeyCombination getShortcut() { - return new KeyCodeCombination(KeyCode.C, KeyCombination.ALT_DOWN, KeyCombination.SHORTCUT_DOWN); - } - @Override public void execute(OpenFileSystemModel model, List entries) { var s = entries.stream() @@ -122,6 +122,12 @@ public class CopyPathAction implements BrowserAction, BranchAction { } }, new LeafAction() { + @Override + public KeyCombination getShortcut() { + return new KeyCodeCombination( + KeyCode.C, KeyCombination.SHIFT_DOWN, KeyCombination.SHORTCUT_DOWN); + } + @Override public String getName(OpenFileSystemModel model, List entries) { if (entries.size() == 1) { @@ -136,12 +142,6 @@ public class CopyPathAction implements BrowserAction, BranchAction { return "File Names"; } - @Override - public KeyCombination getShortcut() { - return new KeyCodeCombination( - KeyCode.C, KeyCombination.SHIFT_DOWN, KeyCombination.SHORTCUT_DOWN); - } - @Override public void execute(OpenFileSystemModel model, List entries) { var s = entries.stream() diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java index 1ed2e48e8..f2b1fa34c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/DeleteAction.java @@ -28,13 +28,13 @@ public class DeleteAction implements LeafAction { } @Override - public Category getCategory() { - return Category.MUTATION; + public Node getIcon(OpenFileSystemModel model, List entries) { + return new FontIcon("mdi2d-delete"); } @Override - public Node getIcon(OpenFileSystemModel model, List entries) { - return new FontIcon("mdi2d-delete"); + public Category getCategory() { + return Category.MUTATION; } @Override @@ -44,6 +44,11 @@ public class DeleteAction implements LeafAction { @Override public String getName(OpenFileSystemModel model, List entries) { - return "Delete" + (entries.stream().allMatch(browserEntry -> browserEntry.getRawFileEntry().getKind() == FileKind.LINK) ? " link" : ""); + return "Delete" + + (entries.stream() + .allMatch(browserEntry -> + browserEntry.getRawFileEntry().getKind() == FileKind.LINK) + ? " link" + : ""); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java index 4f1e86c2c..84591c684 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/EditFileAction.java @@ -20,14 +20,20 @@ public class EditFileAction implements LeafAction { } } + @Override + public Node getIcon(OpenFileSystemModel model, List entries) { + return new FontIcon("mdi2p-pencil"); + } + @Override public Category getCategory() { return Category.OPEN; } @Override - public Node getIcon(OpenFileSystemModel model, List entries) { - return new FontIcon("mdi2p-pencil"); + public String getName(OpenFileSystemModel model, List entries) { + var e = AppPrefs.get().externalEditor().getValue(); + return "Edit with " + (e != null ? e.toTranslatedString().getValue() : "?"); } @Override @@ -40,10 +46,4 @@ public class EditFileAction implements LeafAction { var e = AppPrefs.get().externalEditor().getValue(); return e != null; } - - @Override - public String getName(OpenFileSystemModel model, List entries) { - var e = AppPrefs.get().externalEditor().getValue(); - return "Edit with " + (e != null ? e.toTranslatedString().getValue() : "?"); - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java index d0f8df9c0..27df7389f 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/FileTypeAction.java @@ -11,16 +11,16 @@ import java.util.List; public interface FileTypeAction extends BrowserAction { + @Override + default Node getIcon(OpenFileSystemModel model, List entries) { + return BrowserIcons.createIcon(getType()).createRegion(); + } + @Override default boolean isApplicable(OpenFileSystemModel model, List entries) { var t = getType(); return entries.stream().allMatch(entry -> t.matches(entry.getRawFileEntry())); } - @Override - default Node getIcon(OpenFileSystemModel model, List entries) { - return BrowserIcons.createIcon(getType()).createRegion(); - } - FileType getType(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java index ca2fcd77d..337f77747 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/FollowLinkAction.java @@ -3,8 +3,8 @@ package io.xpipe.ext.base.browser; import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.action.LeafAction; -import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileKind; +import io.xpipe.core.store.FileNames; import javafx.scene.Node; import org.kordamp.ikonli.javafx.FontIcon; @@ -12,35 +12,38 @@ import java.util.List; public class FollowLinkAction implements LeafAction { - @Override - public boolean automaticallyResolveLinks() { - return false; - } - @Override public void execute(OpenFileSystemModel model, List entries) { - var target = FileNames.getParent(entries.getFirst().getRawFileEntry().resolved().getPath()); + var target = FileNames.getParent( + entries.getFirst().getRawFileEntry().resolved().getPath()); model.cdAsync(target); } - @Override - public Category getCategory() { - return Category.OPEN; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2a-arrow-top-right-thick"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return entries.size() == 1 - && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.LINK && entry.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); + public Category getCategory() { + return Category.OPEN; } @Override public String getName(OpenFileSystemModel model, List entries) { return "Follow link"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return entries.size() == 1 + && entries.stream() + .allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.LINK + && entry.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); + } + + @Override + public boolean automaticallyResolveLinks() { + return false; + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java index e49c0ec26..6179ac706 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/ForwardAction.java @@ -13,30 +13,20 @@ import java.util.List; public class ForwardAction implements LeafAction { - public String getId() { - return "forward"; - } - @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { model.forthSync(1); } + public String getId() { + return "forward"; + } + @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("fth-arrow-right"); } - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return false; - } - - @Override - public boolean isActive(OpenFileSystemModel model, List entries) { - return model.getHistory().canGoForthProperty().get(); - } - @Override public KeyCombination getShortcut() { return new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.ALT_DOWN); @@ -46,4 +36,14 @@ public class ForwardAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Forward"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return false; + } + + @Override + public boolean isActive(OpenFileSystemModel model, List entries) { + return model.getHistory().canGoForthProperty().get(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java index 5c329b400..697a13155 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/JarAction.java @@ -16,6 +16,11 @@ public class JarAction extends MultiExecuteAction implements JavaAction, FileTyp return Category.CUSTOM; } + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "java -jar " + BrowserActionFormatter.filesArgument(entries); + } + @Override public boolean isApplicable(OpenFileSystemModel model, List entries) { return super.isApplicable(model, entries) && FileTypeAction.super.isApplicable(model, entries); @@ -26,11 +31,6 @@ public class JarAction extends MultiExecuteAction implements JavaAction, FileTyp return "java -jar " + entry.getOptionallyQuotedFileName(); } - @Override - public String getName(OpenFileSystemModel model, List entries) { - return "java -jar " + BrowserActionFormatter.filesArgument(entries); - } - @Override public FileType getType() { return FileType.byId("jar"); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java index 7804c06f1..f23446ba3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/JavapAction.java @@ -16,13 +16,13 @@ public class JavapAction extends ToFileCommandAction implements FileTypeAction, } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return super.isApplicable(model, entries) && FileTypeAction.super.isApplicable(model, entries); + public String getName(OpenFileSystemModel model, List entries) { + return "javap -c -p " + BrowserActionFormatter.filesArgument(entries); } @Override - public String getName(OpenFileSystemModel model, List entries) { - return "javap -c -p " + BrowserActionFormatter.filesArgument(entries); + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return super.isApplicable(model, entries) && FileTypeAction.super.isApplicable(model, entries); } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java index 3ca446663..2424a2fd3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/NewItemAction.java @@ -25,8 +25,8 @@ public class NewItemAction implements BrowserAction, BranchAction { } @Override - public String getName(OpenFileSystemModel model, List entries) { - return "New"; + public Category getCategory() { + return Category.MUTATION; } @Override @@ -34,6 +34,11 @@ public class NewItemAction implements BrowserAction, BranchAction { return true; } + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "New"; + } + @Override public boolean isApplicable(OpenFileSystemModel model, List entries) { return entries.size() == 1 @@ -43,20 +48,10 @@ public class NewItemAction implements BrowserAction, BranchAction { .equals(model.getCurrentPath().get()); } - @Override - public Category getCategory() { - return Category.MUTATION; - } - @Override public List getBranchingActions(OpenFileSystemModel model, List entries) { return List.of( new LeafAction() { - @Override - public String getName(OpenFileSystemModel model, List entries) { - return "File"; - } - @Override public void execute(OpenFileSystemModel model, List entries) { var name = new SimpleStringProperty(); @@ -78,13 +73,13 @@ public class NewItemAction implements BrowserAction, BranchAction { public Node getIcon(OpenFileSystemModel model, List entries) { return BrowserIcons.createDefaultFileIcon().createRegion(); } - }, - new LeafAction() { + @Override public String getName(OpenFileSystemModel model, List entries) { - return "Directory"; + return "File"; } - + }, + new LeafAction() { @Override public void execute(OpenFileSystemModel model, List entries) { var name = new SimpleStringProperty(); @@ -106,18 +101,13 @@ public class NewItemAction implements BrowserAction, BranchAction { public Node getIcon(OpenFileSystemModel model, List entries) { return BrowserIcons.createDefaultDirectoryIcon().createRegion(); } - }, - new LeafAction() { + @Override public String getName(OpenFileSystemModel model, List entries) { - return "Symbolic link"; + return "Directory"; } - - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS; - } - + }, + new LeafAction() { @Override public void execute(OpenFileSystemModel model, List entries) { var linkName = new SimpleStringProperty(); @@ -145,6 +135,16 @@ public class NewItemAction implements BrowserAction, BranchAction { public Node getIcon(OpenFileSystemModel model, List entries) { return BrowserIcons.createDefaultFileIcon().createRegion(); } + + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "Symbolic link"; + } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return model.getFileSystem().getShell().orElseThrow().getOsType() != OsType.WINDOWS; + } }); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java index 53f13a73c..f1b9e60dd 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryAction.java @@ -19,20 +19,14 @@ public class OpenDirectoryAction implements LeafAction { model.cdAsync(entries.getFirst().getRawFileEntry().getPath()); } - @Override - public Category getCategory() { - return Category.OPEN; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2f-folder-open"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return entries.size() == 1 - && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY); + public Category getCategory() { + return Category.OPEN; } @Override @@ -44,4 +38,10 @@ public class OpenDirectoryAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Open"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return entries.size() == 1 + && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java index bc316cfd1..4c00907f3 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenDirectoryInNewTabAction.java @@ -20,20 +20,14 @@ public class OpenDirectoryInNewTabAction implements LeafAction { null); } - @Override - public Category getCategory() { - return Category.OPEN; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2f-folder-open-outline"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return entries.size() == 1 - && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY); + public Category getCategory() { + return Category.OPEN; } @Override @@ -45,4 +39,10 @@ public class OpenDirectoryInNewTabAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Open in new tab"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return entries.size() == 1 + && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java index b0702f85e..f56495a12 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileDefaultAction.java @@ -22,19 +22,14 @@ public class OpenFileDefaultAction implements LeafAction { } } - @Override - public Category getCategory() { - return Category.OPEN; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2b-book-open-variant"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); + public Category getCategory() { + return Category.OPEN; } @Override @@ -46,4 +41,9 @@ public class OpenFileDefaultAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Open with default application"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java index 7d0e87cee..d9174c3f9 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenFileWithAction.java @@ -22,21 +22,14 @@ public class OpenFileWithAction implements LeafAction { FileOpener.openWithAnyApplication(e.getRawFileEntry()); } - @Override - public Category getCategory() { - return Category.OPEN; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2b-book-open-page-variant-outline"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return !OsType.getLocal().equals(OsType.MACOS) - && entries.size() == 1 - && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); + public Category getCategory() { + return Category.OPEN; } @Override @@ -48,4 +41,11 @@ public class OpenFileWithAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Open with ..."; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return !OsType.getLocal().equals(OsType.MACOS) + && entries.size() == 1 + && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java index 6f2198125..c30caaf92 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenNativeFileDetailsAction.java @@ -4,9 +4,9 @@ import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.action.LeafAction; import io.xpipe.app.util.LocalShell; -import io.xpipe.core.store.FileNames; import io.xpipe.core.process.OsType; import io.xpipe.core.process.ShellControl; +import io.xpipe.core.store.FileNames; import java.util.List; @@ -28,12 +28,15 @@ public class OpenNativeFileDetailsAction implements LeafAction { FileNames.getParent(localFile), FileNames.getFileName(localFile)) : String.format( "$shell = New-Object -ComObject Shell.Application; $shell.NameSpace('%s').Self.InvokeVerb('Properties')", - localFile); + localFile); // The Windows shell invoke verb functionality behaves kinda weirdly and only shows the window as // long as the parent process is running. // So let's keep one process running - LocalShell.getLocalPowershell().command(content).notComplex().execute(); + LocalShell.getLocalPowershell() + .command(content) + .notComplex() + .execute(); } case OsType.Linux linux -> { var dbus = String.format( @@ -48,31 +51,32 @@ public class OpenNativeFileDetailsAction implements LeafAction { """ set fileEntry to (POSIX file "%s") as text tell application "Finder" to open information window of alias fileEntry - """, localFile)) + """, + localFile)) .execute(); } } } } - @Override - public boolean acceptsEmptySelection() { - return true; - } - @Override public Category getCategory() { return Category.NATIVE; } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - var sc = model.getFileSystem().getShell().orElseThrow(); - return sc.getLocalSystemAccess().supportsFileSystemAccess(); + public boolean acceptsEmptySelection() { + return true; } @Override public String getName(OpenFileSystemModel model, List entries) { return "Show details"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + var sc = model.getFileSystem().getShell().orElseThrow(); + return sc.getLocalSystemAccess().supportsFileSystemAccess(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java index 40f4322e0..cdfe1e673 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/OpenTerminalAction.java @@ -15,14 +15,13 @@ import java.util.List; public class OpenTerminalAction implements LeafAction { - public String getId() { - return "openTerminal"; - } - @Override public void execute(OpenFileSystemModel model, List entries) { if (entries.size() == 0) { - model.openTerminalAsync(model.getCurrentDirectory() != null ? model.getCurrentDirectory().getPath() : null); + model.openTerminalAsync( + model.getCurrentDirectory() != null + ? model.getCurrentDirectory().getPath() + : null); return; } @@ -31,14 +30,28 @@ public class OpenTerminalAction implements LeafAction { } } + public String getId() { + return "openTerminal"; + } + + @Override + public Node getIcon(OpenFileSystemModel model, List entries) { + return new FontIcon("mdi2c-console"); + } + @Override public Category getCategory() { return Category.OPEN; } @Override - public Node getIcon(OpenFileSystemModel model, List entries) { - return new FontIcon("mdi2c-console"); + public KeyCombination getShortcut() { + return new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN); + } + + @Override + public String getName(OpenFileSystemModel model, List entries) { + return "Open in terminal"; } @Override @@ -51,14 +64,4 @@ public class OpenTerminalAction implements LeafAction { var t = AppPrefs.get().terminalType().getValue(); return t != null; } - - @Override - public KeyCombination getShortcut() { - return new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN); - } - - @Override - public String getName(OpenFileSystemModel model, List entries) { - return "Open in terminal"; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java index a81aa89c8..220a7c238 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/PasteAction.java @@ -33,24 +33,14 @@ public class PasteAction implements LeafAction { model.dropFilesIntoAsync(target, files, true); } - @Override - public Category getCategory() { - return Category.COPY_PASTE; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2c-content-paste"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return (entries.size() == 1 && entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY)) || entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); - } - - @Override - public boolean isActive(OpenFileSystemModel model, List entries) { - return BrowserClipboard.retrieveCopy() != null; + public Category getCategory() { + return Category.COPY_PASTE; } @Override @@ -67,4 +57,17 @@ public class PasteAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Paste"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return (entries.size() == 1 + && entries.stream() + .allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.DIRECTORY)) + || entries.stream().allMatch(entry -> entry.getRawFileEntry().getKind() == FileKind.FILE); + } + + @Override + public boolean isActive(OpenFileSystemModel model, List entries) { + return BrowserClipboard.retrieveCopy() != null; + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java index 070ccaadf..151007c7f 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RefreshDirectoryAction.java @@ -13,18 +13,13 @@ import java.util.List; public class RefreshDirectoryAction implements LeafAction { - public String getId() { - return "refresh"; - } - @Override public void execute(OpenFileSystemModel model, List entries) throws Exception { model.refreshSync(); } - @Override - public boolean isActive(OpenFileSystemModel model, List entries) { - return !model.getInOverview().get(); + public String getId() { + return "refresh"; } @Override @@ -32,11 +27,6 @@ public class RefreshDirectoryAction implements LeafAction { return new FontIcon("mdmz-refresh"); } - @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return false; - } - @Override public KeyCombination getShortcut() { return new KeyCodeCombination(KeyCode.F5); @@ -46,4 +36,14 @@ public class RefreshDirectoryAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Refresh"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return false; + } + + @Override + public boolean isActive(OpenFileSystemModel model, List entries) { + return !model.getInOverview().get(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java index d1f6bd533..416e4e341 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RenameAction.java @@ -19,24 +19,14 @@ public class RenameAction implements LeafAction { model.getFileList().getEditing().setValue(entries.getFirst()); } - @Override - public boolean automaticallyResolveLinks() { - return false; - } - - @Override - public Category getCategory() { - return Category.MUTATION; - } - @Override public Node getIcon(OpenFileSystemModel model, List entries) { return new FontIcon("mdi2r-rename-box"); } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return entries.size() == 1 && entries.getFirst().getRawFileEntry().getKind() != FileKind.LINK; + public Category getCategory() { + return Category.MUTATION; } @Override @@ -48,4 +38,14 @@ public class RenameAction implements LeafAction { public String getName(OpenFileSystemModel model, List entries) { return "Rename"; } + + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return entries.size() == 1 && entries.getFirst().getRawFileEntry().getKind() != FileKind.LINK; + } + + @Override + public boolean automaticallyResolveLinks() { + return false; + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java index bc58933ce..3e87786ef 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/RunAction.java @@ -44,13 +44,13 @@ public class RunAction extends MultiExecuteAction { } @Override - public Category getCategory() { - return Category.CUSTOM; + public Node getIcon(OpenFileSystemModel model, List entries) { + return new FontIcon("mdi2p-play"); } @Override - public Node getIcon(OpenFileSystemModel model, List entries) { - return new FontIcon("mdi2p-play"); + public Category getCategory() { + return Category.CUSTOM; } @Override diff --git a/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java b/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java index 8801065ec..50a787306 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/browser/UnzipAction.java @@ -4,8 +4,8 @@ import io.xpipe.app.browser.BrowserEntry; import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.action.ExecuteApplicationAction; import io.xpipe.app.browser.icon.FileType; -import io.xpipe.core.store.FileNames; import io.xpipe.core.process.OsType; +import io.xpipe.core.store.FileNames; import java.util.List; @@ -17,9 +17,8 @@ public class UnzipAction extends ExecuteApplicationAction implements FileTypeAct } @Override - public boolean isApplicable(OpenFileSystemModel model, List entries) { - return FileTypeAction.super.isApplicable(model, entries) - && !model.getFileSystem().getShell().orElseThrow().getOsType().equals(OsType.WINDOWS); + protected boolean refresh() { + return true; } @Override @@ -28,11 +27,6 @@ public class UnzipAction extends ExecuteApplicationAction implements FileTypeAct + FileNames.quoteIfNecessary(FileNames.getBaseName(entry.getFileName())); } - @Override - protected boolean refresh() { - return true; - } - @Override public Category getCategory() { return Category.CUSTOM; @@ -43,6 +37,12 @@ public class UnzipAction extends ExecuteApplicationAction implements FileTypeAct return "unzip [...]"; } + @Override + public boolean isApplicable(OpenFileSystemModel model, List entries) { + return FileTypeAction.super.isApplicable(model, entries) + && !model.getFileSystem().getShell().orElseThrow().getOsType().equals(OsType.WINDOWS); + } + @Override public FileType getType() { return FileType.byId("zip"); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java index 586739eb1..46be8027c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java @@ -14,26 +14,21 @@ import java.util.function.Supplier; @Getter public enum PredefinedScriptStore { - CLINK_SETUP( - "Clink Setup", - () -> SimpleScriptStore.builder() - .group(PredefinedScriptGroup.CLINK.getEntry()) - .minimumDialect(ShellDialects.CMD) - .commands(file("clink.bat")) - .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) - .build()), - CLINK_INJECT( - "Clink Inject", - () -> SimpleScriptStore.builder() - .group(PredefinedScriptGroup.CLINK.getEntry()) - .minimumDialect(ShellDialects.CMD) - .script(CLINK_SETUP.getEntry()) - .commands( - """ + CLINK_SETUP("Clink Setup", () -> SimpleScriptStore.builder() + .group(PredefinedScriptGroup.CLINK.getEntry()) + .minimumDialect(ShellDialects.CMD) + .commands(file("clink.bat")) + .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) + .build()), + CLINK_INJECT("Clink Inject", () -> SimpleScriptStore.builder() + .group(PredefinedScriptGroup.CLINK.getEntry()) + .minimumDialect(ShellDialects.CMD) + .script(CLINK_SETUP.getEntry()) + .commands(""" clink inject --quiet """) - .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) - .build()), + .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) + .build()), STARSHIP_BASH("Starship Bash", () -> SimpleScriptStore.builder() .group(PredefinedScriptGroup.STARSHIP.getEntry()) .minimumDialect(ShellDialects.BASH) @@ -52,36 +47,23 @@ public enum PredefinedScriptStore { .commands(file("starship_fish.fish")) .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) .build()), - STARSHIP_CMD( - "Starship Cmd", - () -> SimpleScriptStore.builder() - .group(PredefinedScriptGroup.STARSHIP.getEntry()) - .minimumDialect(ShellDialects.CMD) - .script(CLINK_SETUP.getEntry()) - .commands(file(("starship_cmd.bat"))) - .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) - .build()), - STARSHIP_POWERSHELL( - "Starship Powershell", - () -> SimpleScriptStore.builder() - .group(PredefinedScriptGroup.STARSHIP.getEntry()) - .minimumDialect(ShellDialects.POWERSHELL) - .commands(file("starship_powershell.ps1")) - .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) - .build()); - - public static String file(String name) { - AtomicReference string = new AtomicReference<>(); - AppResources.with("io.xpipe.ext.base", "scripts/" + name, var1 -> { - string.set(Files.readString(var1)); - }); - return string.get(); - } + STARSHIP_CMD("Starship Cmd", () -> SimpleScriptStore.builder() + .group(PredefinedScriptGroup.STARSHIP.getEntry()) + .minimumDialect(ShellDialects.CMD) + .script(CLINK_SETUP.getEntry()) + .commands(file(("starship_cmd.bat"))) + .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) + .build()), + STARSHIP_POWERSHELL("Starship Powershell", () -> SimpleScriptStore.builder() + .group(PredefinedScriptGroup.STARSHIP.getEntry()) + .minimumDialect(ShellDialects.POWERSHELL) + .commands(file("starship_powershell.ps1")) + .executionType(SimpleScriptStore.ExecutionType.TERMINAL_ONLY) + .build()); private final String name; private final Supplier scriptStore; private final UUID uuid; - @Setter private DataStoreEntryRef entry; @@ -90,4 +72,12 @@ public enum PredefinedScriptStore { this.scriptStore = scriptStore; this.uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8)); } + + public static String file(String name) { + AtomicReference string = new AtomicReference<>(); + AppResources.with("io.xpipe.ext.base", "scripts/" + name, var1 -> { + string.set(Files.readString(var1)); + }); + return string.get(); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java index 7e61939dd..ec2977562 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptGroupStoreProvider.java @@ -47,6 +47,22 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { return new DenseStoreEntryComp(sec.getWrapper(), true, dropdown); } + @Override + public Comp stateDisplay(StoreEntryWrapper w) { + return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); + } + + @Override + public CreationCategory getCreationCategory() { + return CreationCategory.SCRIPT; + } + + @Override + public DataStoreEntry getDisplayParent(DataStoreEntry store) { + ScriptGroupStore scriptStore = store.getStore().asNeeded(); + return scriptStore.getParent() != null ? scriptStore.getParent().get() : null; + } + @SneakyThrows @Override public GuiDialog guiDialog(DataStoreEntry entry, Property store) { @@ -62,7 +78,12 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { .description("scriptGroupDescription") .addComp( new DataStoreChoiceComp<>( - DataStoreChoiceComp.Mode.OTHER, entry, group, ScriptGroupStore.class, null, StoreViewState.get().getAllScriptsCategory()), + DataStoreChoiceComp.Mode.OTHER, + entry, + group, + ScriptGroupStore.class, + null, + StoreViewState.get().getAllScriptsCategory()), group) .bind( () -> { @@ -76,9 +97,14 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { } @Override - public DataStoreEntry getDisplayParent(DataStoreEntry store) { - ScriptGroupStore scriptStore = store.getStore().asNeeded(); - return scriptStore.getParent() != null ? scriptStore.getParent().get() : null; + public ObservableValue informationString(StoreEntryWrapper wrapper) { + ScriptGroupStore scriptStore = wrapper.getEntry().getStore().asNeeded(); + return new SimpleStringProperty(scriptStore.getDescription()); + } + + @Override + public String getDisplayIconFileName(DataStore store) { + return "proc:shellEnvironment_icon.svg"; } @Override @@ -86,27 +112,6 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { return ScriptGroupStore.builder().build(); } - @Override - public Comp stateDisplay(StoreEntryWrapper w) { - return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); - } - - @Override - public CreationCategory getCreationCategory() { - return CreationCategory.SCRIPT; - } - - @Override - public String getDisplayIconFileName(DataStore store) { - return "proc:shellEnvironment_icon.svg"; - } - - @Override - public ObservableValue informationString(StoreEntryWrapper wrapper) { - ScriptGroupStore scriptStore = wrapper.getEntry().getStore().asNeeded(); - return new SimpleStringProperty(scriptStore.getDescription()); - } - @Override public List getPossibleNames() { return List.of("scriptGroup"); @@ -116,5 +121,4 @@ public class ScriptGroupStoreProvider implements DataStoreProvider { public List> getStoreClasses() { return List.of(ScriptGroupStore.class); } - } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java index 911433c20..a8e6ebd7b 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java @@ -26,11 +26,19 @@ import java.util.*; @AllArgsConstructor public abstract class ScriptStore extends JacksonizedValue implements DataStore, StatefulDataStore { + protected final DataStoreEntryRef group; + @Singular + protected final List> scripts; + protected final String description; + public static ShellControl controlWithDefaultScripts(ShellControl pc) { return controlWithScripts(pc, getDefaultInitScripts(), getDefaultBringScripts()); } - public static ShellControl controlWithScripts(ShellControl pc, List> initScripts, List> bringScripts) { + public static ShellControl controlWithScripts( + ShellControl pc, + List> initScripts, + List> bringScripts) { try { var initFlattened = flatten(initScripts); var bringFlattened = flatten(bringScripts); @@ -45,12 +53,13 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, var dir = initScriptsDirectory(shellControl, bringFlattened); if (dir != null) { - shellControl.withInitSnippet(new SimpleScriptSnippet(shellControl.getShellDialect().addToPathVariableCommand(List.of(dir), true), + shellControl.withInitSnippet(new SimpleScriptSnippet( + shellControl.getShellDialect().addToPathVariableCommand(List.of(dir), true), ScriptSnippet.ExecutionType.TERMINAL_ONLY)); } }); return pc; - } catch (StackOverflowError t) { + } catch (StackOverflowError t) { throw new RuntimeException("Unable to set up scripts. Is there a circular script dependency?", t); } catch (Throwable t) { throw new RuntimeException("Unable to set up scripts", t); @@ -71,22 +80,36 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, }); } - private static String initScriptsDirectory(ShellControl proc, List scriptStores) throws Exception { + private static String initScriptsDirectory(ShellControl proc, List scriptStores) + throws Exception { if (scriptStores.isEmpty()) { return null; } - var applicable = scriptStores.stream().filter(simpleScriptStore -> simpleScriptStore.getMinimumDialect().isCompatibleTo(proc.getShellDialect())).toList(); + var applicable = scriptStores.stream() + .filter(simpleScriptStore -> + simpleScriptStore.getMinimumDialect().isCompatibleTo(proc.getShellDialect())) + .toList(); if (applicable.isEmpty()) { return null; } - var refs = applicable.stream().map(scriptStore -> { - return DataStorage.get().getStoreEntries().stream().filter(dataStoreEntry -> dataStoreEntry.getStore() == scriptStore).findFirst().map(entry -> entry.ref()); - }).flatMap(Optional::stream).toList(); - var hash = refs.stream().mapToInt(value -> value.get().getName().hashCode() + value.getStore().hashCode()).sum(); + var refs = applicable.stream() + .map(scriptStore -> { + return DataStorage.get().getStoreEntries().stream() + .filter(dataStoreEntry -> dataStoreEntry.getStore() == scriptStore) + .findFirst() + .map(entry -> entry.ref()); + }) + .flatMap(Optional::stream) + .toList(); + var hash = refs.stream() + .mapToInt(value -> + value.get().getName().hashCode() + value.getStore().hashCode()) + .sum(); var xpipeHome = XPipeInstallation.getDataDir(proc); - var targetDir = FileNames.join(xpipeHome, "scripts", proc.getShellDialect().getId()); + var targetDir = + FileNames.join(xpipeHome, "scripts", proc.getShellDialect().getId()); var hashFile = FileNames.join(targetDir, "hash"); var d = proc.getShellDialect(); if (d.createFileExistsCommand(proc, hashFile).executeAndCheck()) { @@ -160,12 +183,33 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, return sorted; } - protected final DataStoreEntryRef group; + @Override + public Class getStateClass() { + return State.class; + } - @Singular - protected final List> scripts; + @Override + public void checkComplete() throws Throwable { + Validators.isType(group, ScriptGroupStore.class); + if (scripts != null) { + Validators.contentNonNull(scripts); + } - protected final String description; + // Prevent possible stack overflow + // for (DataStoreEntryRef s : getEffectiveScripts()) { + // s.checkComplete(); + // } + } + + SequencedCollection queryFlattenedScripts() { + var seen = new LinkedHashSet(); + queryFlattenedScripts(seen); + return seen; + } + + protected abstract void queryFlattenedScripts(LinkedHashSet all); + + public abstract List> getEffectiveScripts(); @FieldDefaults(level = AccessLevel.PRIVATE) @Setter @@ -183,32 +227,4 @@ public abstract class ScriptStore extends JacksonizedValue implements DataStore, bringToShell = s.bringToShell; } } - - @Override - public Class getStateClass() { - return State.class; - } - - @Override - public void checkComplete() throws Throwable { - Validators.isType(group, ScriptGroupStore.class); - if (scripts != null) { - Validators.contentNonNull(scripts); - } - - // Prevent possible stack overflow -// for (DataStoreEntryRef s : getEffectiveScripts()) { -// s.checkComplete(); -// } - } - - SequencedCollection queryFlattenedScripts() { - var seen = new LinkedHashSet(); - queryFlattenedScripts(seen); - return seen; - } - - protected abstract void queryFlattenedScripts(LinkedHashSet all); - - public abstract List> getEffectiveScripts(); } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreTypeChoiceComp.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreTypeChoiceComp.java index 0f2b2743e..b8eee04cd 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreTypeChoiceComp.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreTypeChoiceComp.java @@ -26,7 +26,6 @@ public class ScriptStoreTypeChoiceComp extends SimpleComp { Arrays.stream(available).forEach(executionType -> { map.put(executionType, AppI18n.observable(executionType.getId())); }); - return new ToggleGroupComp<>(selected, new SimpleObjectProperty<>(map)) - .createRegion(); + return new ToggleGroupComp<>(selected, new SimpleObjectProperty<>(map)).createRegion(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java index dc16f5027..d8647d3ac 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStore.java @@ -22,6 +22,10 @@ import java.util.stream.Collectors; @JsonTypeName("script") public class SimpleScriptStore extends ScriptStore implements ScriptSnippet { + private final ShellDialect minimumDialect; + private final String commands; + private final ExecutionType executionType; + private String assemble(ShellControl shellControl, ExecutionType type) { var targetType = type == ExecutionType.TERMINAL_ONLY ? shellControl.getOriginalShellDialect() @@ -42,10 +46,21 @@ public class SimpleScriptStore extends ScriptStore implements ScriptSnippet { } @Override - public List> getEffectiveScripts() { - return scripts != null - ? scripts.stream().filter(Objects::nonNull).toList() - : List.of(); + public String content(ShellControl shellControl) { + return assemble(shellControl, executionType); + } + + @Override + public ScriptSnippet.ExecutionType executionType() { + return executionType; + } + + @Override + public void checkComplete() throws Throwable { + Validators.nonNull(group); + super.checkComplete(); + Validators.nonNull(executionType); + Validators.nonNull(minimumDialect); } public void queryFlattenedScripts(LinkedHashSet all) { @@ -61,24 +76,7 @@ public class SimpleScriptStore extends ScriptStore implements ScriptSnippet { } @Override - public String content(ShellControl shellControl) { - return assemble(shellControl, executionType); - } - - @Override - public ScriptSnippet.ExecutionType executionType() { - return executionType; - } - - private final ShellDialect minimumDialect; - private final String commands; - private final ExecutionType executionType; - - @Override - public void checkComplete() throws Throwable { - Validators.nonNull(group); - super.checkComplete(); - Validators.nonNull(executionType); - Validators.nonNull(minimumDialect); + public List> getEffectiveScripts() { + return scripts != null ? scripts.stream().filter(Objects::nonNull).toList() : List.of(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java index 9c1621d29..aae2856b4 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/SimpleScriptStoreProvider.java @@ -39,28 +39,19 @@ import java.util.stream.Collectors; public class SimpleScriptStoreProvider implements DataStoreProvider { - public String createInsightsMarkdown(DataStore store) { - var s = (SimpleScriptStore) store; - - var builder = MarkdownBuilder.of().addParagraph("XPipe will run the script in ") - .addCode(s.getMinimumDialect() != null ? s.getMinimumDialect().getDisplayName() : "default").add(" shells"); - - if (s.getEffectiveScripts() != null && !s.getEffectiveScripts().isEmpty()) { - builder.add(" with the following scripts prior").addCodeBlock(s.getEffectiveScripts().stream() - .map(scriptStoreDataStoreEntryRef -> scriptStoreDataStoreEntryRef.get().getName()).collect( - Collectors.joining("\n"))); - } - - if (s.getCommands() != null) { - builder.add(" with command contents").addCodeBlock(s.getCommands()); - } - - return builder.build(); + @Override + public boolean editByDefault() { + return true; } @Override - public Comp stateDisplay(StoreEntryWrapper w) { - return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); + public boolean canMoveCategories() { + return false; + } + + @Override + public boolean shouldEdit() { + return true; } @Override @@ -106,14 +97,31 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { } @Override - public boolean shouldEdit() { - return true; + public Comp stateDisplay(StoreEntryWrapper w) { + return new SystemStateComp(new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS)); } - @Override - public DataStoreEntry getDisplayParent(DataStoreEntry store) { - SimpleScriptStore st = store.getStore().asNeeded(); - return st.getGroup().get(); + public String createInsightsMarkdown(DataStore store) { + var s = (SimpleScriptStore) store; + + var builder = MarkdownBuilder.of() + .addParagraph("XPipe will run the script in ") + .addCode(s.getMinimumDialect() != null ? s.getMinimumDialect().getDisplayName() : "default") + .add(" shells"); + + if (s.getEffectiveScripts() != null && !s.getEffectiveScripts().isEmpty()) { + builder.add(" with the following scripts prior") + .addCodeBlock(s.getEffectiveScripts().stream() + .map(scriptStoreDataStoreEntryRef -> + scriptStoreDataStoreEntryRef.get().getName()) + .collect(Collectors.joining("\n"))); + } + + if (s.getCommands() != null) { + builder.add(" with command contents").addCodeBlock(s.getCommands()); + } + + return builder.build(); } @Override @@ -122,26 +130,9 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { } @Override - public String getId() { - return "script"; - } - - @SneakyThrows - @Override - public String getDisplayIconFileName(DataStore store) { - if (store == null) { - return "proc:shellEnvironment_icon.svg"; - } - - SimpleScriptStore st = store.asNeeded(); - return (String) Class.forName( - AppExtensionManager.getInstance() - .getExtendedLayer() - .findModule("io.xpipe.ext.proc") - .orElseThrow(), - "io.xpipe.ext.proc.ShellDialectChoiceComp") - .getDeclaredMethod("getImageName", ShellDialect.class) - .invoke(null, st.getMinimumDialect()); + public DataStoreEntry getDisplayParent(DataStoreEntry store) { + SimpleScriptStore st = store.getStore().asNeeded(); + return st.getGroup().get(); } @SneakyThrows @@ -172,8 +163,8 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { new DataStoreListChoiceComp<>( others, ScriptStore.class, - scriptStore -> !scriptStore.get().equals(entry) && !others.contains(scriptStore), StoreViewState.get().getAllScriptsCategory() - ), + scriptStore -> !scriptStore.get().equals(entry) && !others.contains(scriptStore), + StoreViewState.get().getAllScriptsCategory()), others) .name("minimumShellDialect") .description("minimumShellDialectDescription") @@ -198,7 +189,12 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { .description("scriptGroupDescription") .addComp( new DataStoreChoiceComp<>( - DataStoreChoiceComp.Mode.OTHER, null, group, ScriptGroupStore.class, null, StoreViewState.get().getAllScriptsCategory()), + DataStoreChoiceComp.Mode.OTHER, + null, + group, + ScriptGroupStore.class, + null, + StoreViewState.get().getAllScriptsCategory()), group) .nonNull() .bind( @@ -216,30 +212,6 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { .buildDialog(); } - @Override - public boolean editByDefault() { - return true; - } - - @Override - public boolean canMoveCategories() { - return false; - } - - @Override - public ObservableValue informationString(StoreEntryWrapper wrapper) { - SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded(); - return new SimpleStringProperty((scriptStore.getMinimumDialect() != null - ? scriptStore.getMinimumDialect().getDisplayName() + " " - : "") - + (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY - ? "Terminal" - : scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.DUMB_ONLY - ? "Background" - : "") - + " Snippet"); - } - @Override public void storageInit() { DataStorage.get() @@ -280,8 +252,35 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { } @Override - public List> getStoreClasses() { - return List.of(SimpleScriptStore.class); + public ObservableValue informationString(StoreEntryWrapper wrapper) { + SimpleScriptStore scriptStore = wrapper.getEntry().getStore().asNeeded(); + return new SimpleStringProperty((scriptStore.getMinimumDialect() != null + ? scriptStore.getMinimumDialect().getDisplayName() + " " + : "") + + (scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.TERMINAL_ONLY + ? "Terminal" + : scriptStore.getExecutionType() == SimpleScriptStore.ExecutionType.DUMB_ONLY + ? "Background" + : "") + + " Snippet"); + } + + @SneakyThrows + @Override + public String getDisplayIconFileName(DataStore store) { + if (store == null) { + return "proc:shellEnvironment_icon.svg"; + } + + SimpleScriptStore st = store.asNeeded(); + return (String) Class.forName( + AppExtensionManager.getInstance() + .getExtendedLayer() + .findModule("io.xpipe.ext.proc") + .orElseThrow(), + "io.xpipe.ext.proc.ShellDialectChoiceComp") + .getDeclaredMethod("getImageName", ShellDialect.class) + .invoke(null, st.getMinimumDialect()); } @Override @@ -296,4 +295,14 @@ public class SimpleScriptStoreProvider implements DataStoreProvider { public List getPossibleNames() { return Identifiers.get("script"); } + + @Override + public String getId() { + return "script"; + } + + @Override + public List> getStoreClasses() { + return List.of(SimpleScriptStore.class); + } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/store/StorePauseAction.java b/ext/base/src/main/java/io/xpipe/ext/base/store/StorePauseAction.java index 27f4f2caf..cf924cbda 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/store/StorePauseAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/store/StorePauseAction.java @@ -8,6 +8,37 @@ import lombok.Value; public class StorePauseAction implements ActionProvider { + @Override + public DataStoreCallSite getDataStoreCallSite() { + return new DataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store); + } + + @Override + public Class getApplicableClass() { + return PauseableStore.class; + } + + @Override + public boolean isMajor(DataStoreEntryRef o) { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("pause"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2p-pause"; + } + }; + } + @Value static class Action implements ActionProvider.Action { @@ -23,35 +54,4 @@ public class StorePauseAction implements ActionProvider { entry.getStore().pause(); } } - - @Override - public DataStoreCallSite getDataStoreCallSite() { - return new DataStoreCallSite() { - - @Override - public boolean isMajor(DataStoreEntryRef o) { - return true; - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - return AppI18n.observable("pause"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - return "mdi2p-pause"; - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store); - } - - @Override - public Class getApplicableClass() { - return PauseableStore.class; - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStartAction.java b/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStartAction.java index c39a17ebc..90121ef5a 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStartAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStartAction.java @@ -8,6 +8,37 @@ import lombok.Value; public class StoreStartAction implements ActionProvider { + @Override + public DataStoreCallSite getDataStoreCallSite() { + return new DataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store); + } + + @Override + public Class getApplicableClass() { + return StartableStore.class; + } + + @Override + public boolean isMajor(DataStoreEntryRef o) { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("start"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2p-play"; + } + }; + } + @Value static class Action implements ActionProvider.Action { @@ -23,35 +54,4 @@ public class StoreStartAction implements ActionProvider { entry.getStore().start(); } } - - @Override - public DataStoreCallSite getDataStoreCallSite() { - return new DataStoreCallSite() { - - @Override - public boolean isMajor(DataStoreEntryRef o) { - return true; - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - return AppI18n.observable("start"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - return "mdi2p-play"; - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store); - } - - @Override - public Class getApplicableClass() { - return StartableStore.class; - } - }; - } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStopAction.java b/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStopAction.java index 306abca3f..362525109 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStopAction.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/store/StoreStopAction.java @@ -8,6 +8,37 @@ import lombok.Value; public class StoreStopAction implements ActionProvider { + @Override + public DataStoreCallSite getDataStoreCallSite() { + return new DataStoreCallSite() { + + @Override + public ActionProvider.Action createAction(DataStoreEntryRef store) { + return new Action(store); + } + + @Override + public Class getApplicableClass() { + return StoppableStore.class; + } + + @Override + public boolean isMajor(DataStoreEntryRef o) { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("stop"); + } + + @Override + public String getIcon(DataStoreEntryRef store) { + return "mdi2s-stop"; + } + }; + } + @Value static class Action implements ActionProvider.Action { @@ -23,35 +54,4 @@ public class StoreStopAction implements ActionProvider { entry.getStore().stop(); } } - - @Override - public DataStoreCallSite getDataStoreCallSite() { - return new DataStoreCallSite() { - - @Override - public boolean isMajor(DataStoreEntryRef o) { - return true; - } - - @Override - public ObservableValue getName(DataStoreEntryRef store) { - return AppI18n.observable("stop"); - } - - @Override - public String getIcon(DataStoreEntryRef store) { - return "mdi2s-stop"; - } - - @Override - public ActionProvider.Action createAction(DataStoreEntryRef store) { - return new Action(store); - } - - @Override - public Class getApplicableClass() { - return StoppableStore.class; - } - }; - } } diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index 40d1121b5..760fb6a7b 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -50,7 +50,10 @@ open module io.xpipe.ext.base { UnzipAction, JavapAction, JarAction; - provides ActionProvider with StoreStopAction, StoreStartAction, StorePauseAction, + provides ActionProvider with + StoreStopAction, + StoreStartAction, + StorePauseAction, CloneStoreAction, RefreshStoreAction, ScanAction,