This commit is contained in:
crschnick 2025-04-04 19:35:07 +00:00
parent 120a463d88
commit 7d252c1582
34 changed files with 225 additions and 106 deletions

View file

@ -112,9 +112,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
} catch (BeaconServerException serverException) {
var cause = serverException.getCause() != null ? serverException.getCause() : serverException;
var event = ErrorEvent.fromThrowable(cause).omit().handle();
var link = event.getDocumentationLink() != null
? event.getDocumentationLink().getLink()
: null;
var link = event.getLink();
writeError(exchange, new BeaconServerErrorResponse(cause, link), 500);
return;
} catch (IOException ex) {
@ -136,9 +134,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
return;
} catch (Throwable other) {
var event = ErrorEvent.fromThrowable(other).omit().expected().handle();
var link = event.getDocumentationLink() != null
? event.getDocumentationLink().getLink()
: null;
var link = event.getLink();
writeError(exchange, new BeaconServerErrorResponse(other, link), 500);
return;
}
@ -167,9 +163,7 @@ public class BeaconRequestHandler<T> implements HttpHandler {
}
} catch (Throwable other) {
var event = ErrorEvent.fromThrowable(other).handle();
var link = event.getDocumentationLink() != null
? event.getDocumentationLink().getLink()
: null;
var link = event.getLink();
writeError(exchange, new BeaconServerErrorResponse(other, link), 500);
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
@ -34,7 +34,7 @@ public abstract class BrowserSessionTab {
public abstract String getIcon();
public abstract DataColor getColor();
public abstract DataStoreColor getColor();
public boolean isCloseable() {
return true;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntryRef;
import io.xpipe.core.store.DataStore;
@ -42,7 +42,7 @@ public abstract class BrowserStoreSessionTab<T extends DataStore> extends Browse
}
@Override
public DataColor getColor() {
public DataStoreColor getColor() {
return DataStorage.get().getEffectiveColor(entry.get());
}
}

View file

@ -5,7 +5,7 @@ import io.xpipe.app.browser.BrowserFullSessionModel;
import io.xpipe.app.browser.BrowserSessionTab;
import io.xpipe.app.comp.Comp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import javafx.beans.value.ObservableValue;
@ -42,7 +42,7 @@ public final class BrowserHistoryTabModel extends BrowserSessionTab {
}
@Override
public DataColor getColor() {
public DataStoreColor getColor() {
return null;
}

View file

@ -9,7 +9,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.terminal.TerminalDockComp;
import io.xpipe.app.terminal.TerminalDockModel;
import io.xpipe.app.terminal.TerminalView;
@ -176,7 +176,7 @@ public final class BrowserTerminalDockTabModel extends BrowserSessionTab {
}
@Override
public DataColor getColor() {
public DataStoreColor getColor() {
return null;
}
}

View file

@ -108,8 +108,14 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
}
var handler = DataStorageSyncHandler.getInstance();
var syncedTarget = handler.addDataFile(
source, target, sync.getPerUser().test(source));
var syncedTarget = handler.addDataFile(source, target, sync.getPerUser().test(source));
var pubSource = Path.of(source + ".pub");
if (Files.exists(pubSource)) {
var pubTarget = sync.getTargetLocation().apply(pubSource);
DataStorageSyncHandler.getInstance().addDataFile(pubSource, pubTarget, sync.getPerUser().test(pubSource));
}
Platform.runLater(() -> {
filePath.setValue(FilePath.of(syncedTarget));
});

View file

@ -83,6 +83,10 @@ public class ModalOverlay {
AppDialog.show(this, false);
}
public void hide() {
AppDialog.hide(this);
}
public boolean isShowing() {
return AppDialog.getModalOverlays().contains(this);
}

View file

@ -7,7 +7,7 @@ import io.xpipe.app.comp.base.*;
import io.xpipe.app.core.AppFontSizes;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.util.ClipboardHelper;
@ -187,7 +187,7 @@ public class StoreCategoryComp extends SimpleComp {
});
category.getColor().subscribe((c) -> {
DataColor.applyStyleClasses(c, struc.get());
DataStoreColor.applyStyleClasses(c, struc.get());
});
});
@ -222,7 +222,7 @@ public class StoreCategoryComp extends SimpleComp {
event.consume();
});
color.getItems().add(none);
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
Arrays.stream(DataStoreColor.values()).forEach(dataStoreColor -> {
MenuItem m = new MenuItem();
m.textProperty().bind(AppI18n.observable(dataStoreColor.getId()));
m.setOnAction(event -> {

View file

@ -0,0 +1,3 @@
package io.xpipe.app.comp.store;
public class StoreCategoryConfigComp {}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.util.DerivedObservableList;
@ -35,7 +35,7 @@ public class StoreCategoryWrapper {
private final IntegerProperty shownContainedEntriesCount = new SimpleIntegerProperty();
private final IntegerProperty allContainedEntriesCount = new SimpleIntegerProperty();
private final BooleanProperty expanded = new SimpleBooleanProperty();
private final Property<DataColor> color = new SimpleObjectProperty<>();
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
private final BooleanProperty largeCategoryOptimizations = new SimpleBooleanProperty();
private StoreCategoryWrapper cachedParent;

View file

@ -3,6 +3,7 @@ package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.ModalButton;
import io.xpipe.app.comp.base.ModalOverlay;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.core.window.AppDialog;
import io.xpipe.app.ext.DataStoreCreationCategory;
import io.xpipe.app.ext.DataStoreProvider;
@ -143,6 +144,16 @@ public class StoreCreationDialog {
modal.hideable(AppI18n.observable(model.storeTypeNameKey() + "Add"), graphic, () -> {
modal.show();
});
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
if (model.getFinished().get() || !modal.isShowing()) {
return;
}
modal.hide();
AppLayoutModel.get().getQueueEntries().add(new AppLayoutModel.QueueEntry(AppI18n.observable(model.storeTypeNameKey() + "Add"), graphic, () -> {
modal.show();
}));
});
modal.setRequireCloseButtonForClose(true);
modal.addButton(new ModalButton(
"docs",

View file

@ -12,7 +12,7 @@ import io.xpipe.app.core.*;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.*;
@ -340,7 +340,7 @@ public abstract class StoreEntryComp extends SimpleComp {
event.consume();
});
color.getItems().add(none);
Arrays.stream(DataColor.values()).forEach(dataStoreColor -> {
Arrays.stream(DataStoreColor.values()).forEach(dataStoreColor -> {
MenuItem m = new MenuItem();
m.textProperty().bind(AppI18n.observable(dataStoreColor.getId()));
m.setOnAction(event -> {

View file

@ -1,10 +1,11 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.LocalStore;
import io.xpipe.app.ext.ShellStore;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreCategory;
import io.xpipe.app.storage.DataStoreEntry;
@ -42,7 +43,7 @@ public class StoreEntryWrapper {
private final BooleanProperty expanded = new SimpleBooleanProperty();
private final Property<Object> persistentState = new SimpleObjectProperty<>();
private final Property<Map<String, Object>> cache = new SimpleObjectProperty<>(Map.of());
private final Property<DataColor> color = new SimpleObjectProperty<>();
private final Property<DataStoreColor> color = new SimpleObjectProperty<>();
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
private final Property<String> summary = new SimpleObjectProperty<>();
private final Property<StoreNotes> notes;
@ -201,7 +202,7 @@ public class StoreEntryWrapper {
customIcon.setValue(entry.getIcon());
iconFile.setValue(entry.getEffectiveIconFile());
busy.setValue(entry.getBusyCounter().get() != 0);
deletable.setValue(entry.getConfiguration().isDeletable());
deletable.setValue(!(entry.getStore() instanceof LocalStore));
sessionActive.setValue(entry.getStore() instanceof SingletonSessionStore<?> ss
&& entry.getStore() instanceof ShellStore
&& ss.isSessionRunning());

View file

@ -4,7 +4,7 @@ import io.xpipe.app.comp.Comp;
import io.xpipe.app.comp.CompStructure;
import io.xpipe.app.comp.base.IconButtonComp;
import io.xpipe.app.comp.base.ListBoxViewComp;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.BindingsHelper;
import io.xpipe.app.util.LabelGraphic;
@ -65,7 +65,7 @@ public abstract class StoreSectionBaseComp extends Comp<CompStructure<VBox>> {
if (section.getDepth() == 1) {
section.getWrapper().getColor().subscribe(val -> {
var newList = new ArrayList<>(vbox.getStyleClass());
newList.removeIf(s -> Arrays.stream(DataColor.values())
newList.removeIf(s -> Arrays.stream(DataStoreColor.values())
.anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
newList.remove("gray");
newList.add("color-box");

View file

@ -56,6 +56,12 @@ public class AppDialog {
show(o, false);
}
public static void hide(ModalOverlay o) {
PlatformThread.runLaterIfNeeded(() -> {
modalOverlays.remove(o);
});
}
public static void showAndWait(ModalOverlay o) {
show(o, true);
}

View file

@ -2,10 +2,11 @@ package io.xpipe.app.issue;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.DocumentationLink;
import io.xpipe.app.util.Hyperlinks;
public interface ErrorAction {
static ErrorAction openDocumentation(DocumentationLink link) {
static ErrorAction openDocumentation(String link) {
return new ErrorAction() {
@Override
public String getName() {
@ -19,7 +20,7 @@ public interface ErrorAction {
@Override
public boolean handle(ErrorEvent event) {
link.open();
Hyperlinks.open(link);
return false;
}
};

View file

@ -42,7 +42,7 @@ public class ErrorEvent {
@Singular
private List<Path> attachments;
private DocumentationLink documentationLink;
private String link;
private String email;
private String userReport;
@ -147,6 +147,10 @@ public class ErrorEvent {
public static class ErrorEventBuilder {
public ErrorEventBuilder documentationLink(DocumentationLink documentationLink) {
return link(documentationLink.getLink());
}
public ErrorEventBuilder term() {
return terminal(true);
}

View file

@ -8,7 +8,6 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppLayoutModel;
import io.xpipe.app.util.LicenseRequiredException;
import io.xpipe.app.util.PlatformThread;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -22,8 +21,6 @@ import javafx.scene.layout.VBox;
import lombok.Getter;
import java.util.concurrent.CountDownLatch;
import static atlantafx.base.theme.Styles.ACCENT;
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
@ -134,8 +131,8 @@ public class ErrorHandlerComp extends SimpleComp {
actionBox.getChildren().add(ac);
}
if (event.getDocumentationLink() != null) {
actionBox.getChildren().add(createActionComp(ErrorAction.openDocumentation(event.getDocumentationLink())));
if (event.getLink() != null) {
actionBox.getChildren().add(createActionComp(ErrorAction.openDocumentation(event.getLink())));
}
if (actionBox.getChildren().size() > 0) {

View file

@ -1,25 +1,86 @@
package io.xpipe.app.password;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.terminal.TerminalLauncher;
import io.xpipe.app.util.*;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellScript;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.UUID;
import java.util.regex.Pattern;
@JsonTypeName("bitwarden")
public class BitwardenPasswordManager extends PasswordManagerFixedCommand {
public class BitwardenPasswordManager implements PasswordManager {
private static final UUID MASTER_PASSWORD_ID = UUID.randomUUID();
private static ShellControl SHELL;
private static synchronized ShellControl getOrStartShell() throws Exception {
if (SHELL == null) {
SHELL = ProcessControlProvider.get().createLocalProcessControl(true);
}
SHELL.start();
return SHELL;
}
@Override
protected ShellScript getScript() {
var s = "bw get password $KEY --nointeraction --raw";
return new ShellScript(s);
public synchronized String retrievePassword(String key) {
try {
CommandSupport.isInLocalPathOrThrow("BitwardenCLI", "bw");
} catch (Exception e) {
ErrorEvent.fromThrowable(e).link("https://bitwarden.com/help/cli/#download-and-install").handle();
return null;
}
try (var sc = getOrStartShell().start()) {
var command = sc.command(CommandBuilder.of().add("bw", "get", "item", "xpipe-test", "--nointeraction"));
var r = command.readStdoutAndStderr();
if (r[1].contains("You are not logged in")) {
var script = ShellScript.lines(
sc.getShellDialect().getEchoCommand("Log in into your Bitwarden account from the CLI:", false),
"bw login"
);
TerminalLauncher.openDirect("Bitwarden login", script);
return null;
}
if (r[1].contains("Vault is locked")) {
var pw = SecretManager.retrieve(new SecretRetrievalStrategy.Prompt(), "Unlock vault with your Bitwarden master password", MASTER_PASSWORD_ID, 0, true);
if (pw == null) {
return null;
}
var cmd = sc.command(CommandBuilder.of().add("bw", "unlock", "--passwordenv", "BW_PASSWORD")
.fixedEnvironment("BW_PASSWORD", pw.getSecretValue()));
cmd.setSensitive();
var out = cmd.readStdoutOrThrow();
var matcher = Pattern.compile("export BW_SESSION=\"(.+)\"").matcher(out);
if (matcher.find()) {
var sessionKey = matcher.group(1);
sc.view().setSensitiveEnvironmentVariable("BW_SESSION", sessionKey);
} else {
return null;
}
}
var b = CommandBuilder.of().add("bw", "get", "password").addLiteral(key).add("--nointeraction", "--raw");
return sc.command(b).readStdoutOrThrow();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
return null;
}
}
@Override
public String getDocsLink() {
return "https://bitwarden.com/help/cli/#get";
return "https://bitwarden.com/help/cli/#download-and-install";
}
@Override
public String getKeyPlaceholder() {
return "Item ID";
return "Item name";
}
}

View file

@ -883,7 +883,7 @@ public abstract class DataStorage {
return false;
}
public DataColor getEffectiveColor(DataStoreEntry entry) {
public DataStoreColor getEffectiveColor(DataStoreEntry entry) {
var cat = getStoreCategoryIfPresent(entry.getCategoryUuid()).orElseThrow();
var root = getRootForEntry(entry, cat);
if (root.getColor() != null) {

View file

@ -39,7 +39,7 @@ public class DataStoreCategory extends StorageElement {
String name,
Instant lastUsed,
Instant lastModified,
DataColor color,
DataStoreColor color,
boolean dirty,
UUID parentCategory,
StoreSortMode sortMode,
@ -102,7 +102,7 @@ public class DataStoreCategory extends StorageElement {
var color = Optional.ofNullable(json.get("color"))
.map(node -> {
try {
return mapper.treeToValue(node, DataColor.class);
return mapper.treeToValue(node, DataStoreColor.class);
} catch (JsonProcessingException e) {
return null;
}

View file

@ -0,0 +1,17 @@
package io.xpipe.app.storage;
import lombok.Builder;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
@Value
@Builder
@Jacksonized
public class DataStoreCategoryConfig {
DataStoreColor color;
Boolean allowInitScripts;
Boolean allowTerminalPromptScripts;
}

View file

@ -10,7 +10,7 @@ import java.util.ArrayList;
import java.util.Arrays;
@Getter
public enum DataColor {
public enum DataStoreColor {
@JsonProperty("red")
RED("red", "\uD83D\uDD34", Color.DARKRED),
@ -27,7 +27,7 @@ public enum DataColor {
private final String emoji;
private final Color terminalColor;
DataColor(String id, String emoji, Color terminalColor) {
DataStoreColor(String id, String emoji, Color terminalColor) {
this.id = id;
this.emoji = emoji;
this.terminalColor = terminalColor;
@ -43,9 +43,9 @@ public enum DataColor {
return "#" + (format(value.getRed()) + format(value.getGreen()) + format(value.getBlue())).toUpperCase();
}
public static void applyStyleClasses(DataColor color, Node node) {
public static void applyStyleClasses(DataStoreColor color, Node node) {
var newList = new ArrayList<>(node.getStyleClass());
newList.removeIf(s -> Arrays.stream(DataColor.values())
newList.removeIf(s -> Arrays.stream(DataStoreColor.values())
.anyMatch(dataStoreColor -> dataStoreColor.getId().equals(s)));
newList.remove("gray");
if (color != null) {

View file

@ -43,9 +43,6 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
DataStore store;
@NonFinal
Configuration configuration;
AtomicInteger busyCounter = new AtomicInteger();
@Getter
@ -88,10 +85,9 @@ public class DataStoreEntry extends StorageElement {
DataStorageNode storeNode,
boolean dirty,
Validity validity,
Configuration configuration,
JsonNode storePersistentState,
boolean expanded,
DataColor color,
DataStoreColor color,
String notes,
Order explicitOrder,
String icon) {
@ -100,7 +96,6 @@ public class DataStoreEntry extends StorageElement {
this.store = store;
this.storeNode = storeNode;
this.validity = validity;
this.configuration = configuration;
this.explicitOrder = explicitOrder;
this.provider = store != null ? DataStoreProviders.byStore(store) : null;
this.storePersistentStateNode = storePersistentState;
@ -125,7 +120,6 @@ public class DataStoreEntry extends StorageElement {
this.icon = icon;
this.storeNode = DataStorageNode.fail();
this.validity = Validity.INCOMPLETE;
this.configuration = Configuration.defaultConfiguration();
this.expanded = false;
this.provider = null;
this.storePersistentStateNode = null;
@ -173,7 +167,6 @@ public class DataStoreEntry extends StorageElement {
storeNode,
true,
validity,
Configuration.defaultConfiguration(),
null,
false,
null,
@ -221,7 +214,7 @@ public class DataStoreEntry extends StorageElement {
var color = Optional.ofNullable(json.get("color"))
.map(node -> {
try {
return mapper.treeToValue(node, DataColor.class);
return mapper.treeToValue(node, DataStoreColor.class);
} catch (JsonProcessingException e) {
return null;
}
@ -254,15 +247,6 @@ public class DataStoreEntry extends StorageElement {
}
})
.orElse(null);
var configuration = Optional.ofNullable(json.get("configuration"))
.map(node -> {
try {
return mapper.treeToValue(node, Configuration.class);
} catch (JsonProcessingException e) {
return Configuration.defaultConfiguration();
}
})
.orElse(Configuration.defaultConfiguration());
var expanded = Optional.ofNullable(stateJson.get("expanded"))
.map(jsonNode -> jsonNode.booleanValue())
.orElse(true);
@ -271,7 +255,7 @@ public class DataStoreEntry extends StorageElement {
color = Optional.ofNullable(stateJson.get("color"))
.map(node -> {
try {
return mapper.treeToValue(node, DataColor.class);
return mapper.treeToValue(node, DataStoreColor.class);
} catch (JsonProcessingException e) {
return null;
}
@ -313,7 +297,6 @@ public class DataStoreEntry extends StorageElement {
node,
false,
store == null ? Validity.LOAD_FAILED : Validity.INCOMPLETE,
configuration,
persistentState,
expanded,
color,
@ -425,11 +408,6 @@ public class DataStoreEntry extends StorageElement {
}
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
notifyUpdate(false, true);
}
public void setCategoryUuid(UUID categoryUuid) {
var changed = !Objects.equals(this.categoryUuid, categoryUuid);
this.categoryUuid = categoryUuid;
@ -464,7 +442,6 @@ public class DataStoreEntry extends StorageElement {
obj.put("categoryUuid", categoryUuid.toString());
obj.set("color", mapper.valueToTree(color));
obj.set("icon", mapper.valueToTree(icon));
obj.set("configuration", mapper.valueToTree(configuration));
ObjectNode stateObj = JsonNodeFactory.instance.objectNode();
stateObj.put("lastUsed", lastUsed.toString());

View file

@ -48,8 +48,6 @@ public class ImpersistentStorage extends DataStorage {
var e = DataStoreEntry.createNew(
LOCAL_ID, DataStorage.DEFAULT_CATEGORY_UUID, "Local Machine", new LocalStore());
e.setConfiguration(
StorageElement.Configuration.builder().deletable(false).build());
storeEntries.put(e, e);
e.validate();
}

View file

@ -221,15 +221,13 @@ public class StandardStorage extends DataStorage {
var e = DataStoreEntry.createNew(
LOCAL_ID, DataStorage.DEFAULT_CATEGORY_UUID, "Local Machine", new LocalStore());
e.setDirectory(getStoresDir().resolve(LOCAL_ID.toString()));
e.setConfiguration(
StorageElement.Configuration.builder().deletable(false).build());
storeEntries.put(e, e);
e.validate();
}
var local = DataStorage.get().getStoreEntry(LOCAL_ID);
if (storeEntriesSet.stream().noneMatch(entry -> entry.getColor() != null)) {
local.setColor(DataColor.BLUE);
local.setColor(DataStoreColor.BLUE);
}
// Reload stores, this time with all entry refs present

View file

@ -1,11 +1,8 @@
package io.xpipe.app.storage;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.Value;
import lombok.experimental.NonFinal;
import lombok.extern.jackson.Jacksonized;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
@ -43,7 +40,7 @@ public abstract class StorageElement {
@Getter
protected boolean expanded;
protected @NonFinal @Getter DataColor color;
protected @NonFinal @Getter DataStoreColor color;
public StorageElement(
Path directory,
@ -51,7 +48,7 @@ public abstract class StorageElement {
String name,
Instant lastUsed,
Instant lastModified,
DataColor color,
DataStoreColor color,
boolean expanded,
boolean dirty) {
this.directory = directory;
@ -98,7 +95,7 @@ public abstract class StorageElement {
FileUtils.deleteDirectory(directory.toFile());
}
public void setColor(DataColor newColor) {
public void setColor(DataStoreColor newColor) {
var changed = !Objects.equals(color, newColor);
this.color = newColor;
if (changed) {
@ -146,15 +143,4 @@ public abstract class StorageElement {
public interface Listener {
void onUpdate();
}
@Builder
@Jacksonized
@Value
public static class Configuration {
boolean deletable;
public static Configuration defaultConfiguration() {
return new Configuration(true);
}
}
}

View file

@ -4,7 +4,7 @@ import io.xpipe.app.core.AppProperties;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.app.util.LicenseProvider;
@ -29,7 +29,7 @@ import java.util.UUID;
@Value
@With
public class TerminalLaunchConfiguration {
DataColor color;
DataStoreColor color;
String coloredTitle;
String cleanTitle;
boolean preferTabs;

View file

@ -70,6 +70,26 @@ public class TerminalLauncher {
return ScriptHelper.createExecScriptRaw(processControl, file, content);
}
public static void openDirect(
String title, CommandBuilder command)
throws Exception {
openDirect(title, sc -> command.buildFull(sc), AppPrefs.get().terminalType().getValue());
}
public static void openDirect(
String title,
ShellScript command)
throws Exception {
openDirect(title, sc -> command.toString(), AppPrefs.get().terminalType().getValue());
}
public static void openDirect(
String title,
FailableFunction<ShellControl, ShellScript, Exception> command)
throws Exception {
openDirect(title, sc -> command.apply(sc).toString(), AppPrefs.get().terminalType().getValue());
}
public static void openDirect(
String title, FailableFunction<ShellControl, String, Exception> command, ExternalTerminalType type)
throws Exception {

View file

@ -3,6 +3,7 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FilePath;
import io.xpipe.core.util.FailableSupplier;
@ -58,4 +59,20 @@ public class CommandSupport {
+ (connection != null ? " on system " + connection.getName() : "")));
}
}
public static boolean isInLocalPath(String executable) throws Exception {
var cmd = OsType.getLocal() == OsType.WINDOWS ? "where" : "which";
var r = LocalExec.readStdoutIfPossible(cmd, executable);
return r.isPresent();
}
public static void isInLocalPathOrThrow(String displayName, String executable) throws Exception {
var present = isInLocalPath(executable);
var prefix = displayName != null ? displayName + " executable \"" + executable + "\"" : "\"" + executable + "\" executable";
if (present) {
return;
}
throw ErrorEvent.expected(new IOException(
prefix + " not found in PATH. Install the executable, add it to the PATH, and refresh the environment by restarting XPipe to fix this."));
}
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.util;
public enum DocumentationLink {
INDEX(""),
TTY("troubleshoot/tty"),
WINDOWS_SSH("troubleshoot/windows-ssh"),

View file

@ -4,7 +4,6 @@ import io.xpipe.core.store.FileEntry;
import io.xpipe.core.store.FilePath;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.util.NewLine;
import io.xpipe.core.util.SecretValue;
import io.xpipe.core.util.StreamCharset;
import java.nio.charset.Charset;
@ -112,8 +111,6 @@ public interface ShellDialect {
String getSetEnvironmentVariableCommand(String variable, String value);
String setSecretEnvironmentVariableCommand(ShellControl sc, String variable, SecretValue value) throws Exception;
String getEchoCommand(String s, boolean toErrorStream);
String getPrintVariableCommand(String name);

View file

@ -147,9 +147,22 @@ public class ShellView {
cmd.executeAndCheck();
}
public String environmentVariable(String name) throws Exception {
public String getEnvironmentVariable(String name) throws Exception {
return shellControl
.command(shellControl.getShellDialect().getPrintEnvironmentVariableCommand(name))
.readStdoutOrThrow();
}
public void setEnvironmentVariable(String name, String value) throws Exception {
shellControl
.command(shellControl.getShellDialect().getSetEnvironmentVariableCommand(name, value))
.execute();
}
public void setSensitiveEnvironmentVariable(String name, String value) throws Exception {
var command = shellControl.command(shellControl.getShellDialect().getSetEnvironmentVariableCommand(name, value));
command.setSensitive();
command
.execute();
}
}

View file

@ -13,6 +13,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
@ -51,6 +52,12 @@ public class SyncedIdentityStoreProvider extends IdentityStoreProvider {
var source = Path.of(f.getFile().toAbsoluteFilePath(null).toString());
var target = Path.of("keys", f.getFile().toAbsoluteFilePath(null).getFileName());
DataStorageSyncHandler.getInstance().addDataFile(source, target, newValue);
var pub = Path.of(source + ".pub");
var pubTarget = Path.of("keys", f.getFile().toAbsoluteFilePath(null).getFileName() + ".pub");
if (Files.exists(pub)) {
DataStorageSyncHandler.getInstance().addDataFile(pub, pubTarget, newValue);
}
});
return new OptionsBuilder()