Rework terminal launching, path caching, and state management

This commit is contained in:
crschnick 2023-12-19 06:50:15 +00:00
parent 4fda66e7db
commit 2af59af190
27 changed files with 180 additions and 131 deletions

View file

@ -1,35 +1,20 @@
package io.xpipe.app.browser;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.ShellControlCache;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialect;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
@Getter
public class OpenFileSystemCache {
public class OpenFileSystemCache extends ShellControlCache {
private final OpenFileSystemModel model;
private final Map<String, Boolean> installedApplications = new HashMap<>();
private final Map<String, Object> multiPurposeCache = new HashMap<>();
private String username;
private final String username;
public OpenFileSystemCache(OpenFileSystemModel model) {
public OpenFileSystemCache(OpenFileSystemModel model) throws Exception {
super(model.getFileSystem().getShell().orElseThrow());
this.model = model;
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) multiPurposeCache.get(key);
}
public void set(String key, Object value) {
multiPurposeCache.put(key, value);
}
public void init() throws Exception {
ShellControl sc = model.getFileSystem().getShell().get();
ShellDialect d = sc.getShellDialect();
username = d.printUsernameCommand(sc).readStdoutOrThrow();
@ -38,18 +23,4 @@ public class OpenFileSystemCache {
public boolean isRoot() {
return username.equals("root");
}
public boolean isApplicationInPath(String app) {
if (!installedApplications.containsKey(app)) {
try {
var b = ApplicationHelper.isInPath(
model.getFileSystem().getShell().orElseThrow(), app);
installedApplications.put(app, b);
} catch (Exception e) {
installedApplications.put(app, false);
}
}
return installedApplications.get(app);
}
}

View file

@ -35,7 +35,7 @@ public final class OpenFileSystemModel {
private final BooleanProperty busy = new SimpleBooleanProperty();
private final BrowserModel browserModel;
private OpenFileSystemSavedState savedState;
private final OpenFileSystemCache cache = new OpenFileSystemCache(this);
private OpenFileSystemCache cache;
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
private final BooleanProperty inOverview = new SimpleBooleanProperty();
private final String name;
@ -375,7 +375,7 @@ public final class OpenFileSystemModel {
.map(shellControl -> shellControl.hasLocalSystemAccess())
.orElse(false);
this.cache.init();
this.cache = new OpenFileSystemCache(this);
for (BrowserAction b : BrowserAction.ALL) {
b.init(this);
}

View file

@ -345,7 +345,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
.getValue()
.getValidationResult()
.getMessages()
.get(0)
.getFirst()
.getText();
TrackEvent.info(msg);
var newMessage = msg;
@ -360,6 +360,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
ThreadHelper.runAsync(() -> {
try (var b = new BooleanScope(busy).start()) {
DataStorage.get().addStoreEntryInProgress(entry.getValue());
entry.getValue().validateOrThrow();
finished.setValue(true);
PlatformThread.runLaterIfNeeded(parent::next);
@ -375,6 +376,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
ErrorEvent.unreportable(ex);
}
ErrorEvent.fromThrowable(ex).omit().handle();
} finally {
DataStorage.get().removeStoreEntryInProgress(entry.getValue());
}
});
return false;

View file

@ -3,14 +3,15 @@ package io.xpipe.app.core.mode;
import io.xpipe.app.browser.BrowserModel;
import io.xpipe.app.comp.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.issue.*;
import io.xpipe.app.ext.ActionProvider;
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.LicenseProvider;
import io.xpipe.app.util.FileBridge;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.LockedSecretValue;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.JacksonMapper;
public class BaseMode extends OperationMode {
@ -47,7 +48,7 @@ public class BaseMode extends OperationMode {
AppI18n.init();
LicenseProvider.get().init();
AppAntivirusAlert.showIfNeeded();
LocalStore.init();
LocalShell.init();
XPipeDistributionType.init();
AppPrefs.init();
AppCharsets.init();
@ -56,6 +57,7 @@ public class BaseMode extends OperationMode {
DataStorage.init();
AppFileWatcher.init();
FileBridge.init();
ActionProvider.initProviders();
TrackEvent.info("mode", "Finished base components initialization");
initialized = true;
}

View file

@ -4,10 +4,10 @@ import io.xpipe.app.core.*;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.*;
import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation;
@ -184,7 +184,7 @@ public abstract class OperationMode {
public static void restart() {
OperationMode.executeAfterShutdown(() -> {
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, "");
LocalStore.getShell().executeSimpleCommand(exec);
LocalShell.getShell().executeSimpleCommand(exec);
});
}

View file

@ -16,6 +16,16 @@ public interface ActionProvider {
List<ActionProvider> ALL = new ArrayList<>();
static void initProviders() {
for (ActionProvider actionProvider : ALL) {
try {
actionProvider.init();
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).handle();
}
}
}
class Loader implements ModuleLayerLoader {
@Override
@ -51,6 +61,9 @@ public interface ActionProvider {
void execute() throws Exception;
}
default void init() {
}
default String getId() {
return null;
}

View file

@ -2,7 +2,7 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.LocalStore;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
@ -44,7 +44,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
protected Optional<Path> getApplicationPath() {
try (ShellControl pc = LocalStore.getShell().start()) {
try (ShellControl pc = LocalShell.getShell().start()) {
try (var c = pc.command(String.format(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister "
+ "-dump | grep -o \"/.*%s.app\" | grep -v -E \"Caches|TimeMachine|Temporary|.Trash|/Volumes/%s\" | uniq",
@ -100,7 +100,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
public boolean isAvailable() {
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
@ -122,7 +122,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path
try (var cc = LocalStore.getShell()
try (var cc = LocalShell.getShell()
.command(ShellDialects.getPlatformDefault().getWhichCommand(executable))
.start()) {
var out = cc.readStdoutDiscardErr();

View file

@ -3,10 +3,10 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.LocalStore;
import java.io.IOException;
import java.nio.file.Path;
@ -152,7 +152,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@Override
public void launch(Path file) throws Exception {
LocalStore.getShell().executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
LocalShell.getShell().executeSimpleCommand(CommandBuilder.of().add(executable).addFile(file.toString()));
}
@Override

View file

@ -3,15 +3,11 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.MacOsPermissions;
import io.xpipe.app.util.ScriptHelper;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.app.util.*;
import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import lombok.Getter;
import lombok.Value;
@ -28,7 +24,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalStore.getShell()
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("start")
.addQuoted(configuration.getTitle())
@ -99,7 +95,7 @@ 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.getTitle());
LocalStore.getShell()
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("wt", "-w", "1", "nt", "--title")
.addQuoted(fixedName)
@ -127,7 +123,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.addQuoted("colors.primary.background='%s'"
.formatted(configuration.getColor().toHexString()));
}
LocalStore.getShell()
LocalShell.getShell()
.executeSimpleCommand(b.add("-t")
.addQuoted(configuration.getTitle())
.add("-e")
@ -242,7 +238,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
ApplicationHelper.checkIsInPath(pc, executable, toTranslatedString(), null);
var toExecute = CommandBuilder.of()
@ -478,7 +474,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalStore.getShell()
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Alacritty.app")
@ -502,7 +498,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return;
}
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
pc.osascriptCommand(String.format(
"""
if application "Kitty" is running then
@ -594,7 +590,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
var suffix = "\"" + configuration.getScriptFile().replaceAll("\"", "\\\\\"") + "\"";
pc.osascriptCommand(String.format(
"""
@ -627,7 +623,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
var format = custom.toLowerCase(Locale.ROOT).contains("$cmd") ? custom : custom + " $CMD";
try (var pc = LocalStore.getShell()) {
try (var pc = LocalShell.getShell()) {
var toExecute = ApplicationHelper.replaceFileArgument(format, "CMD", configuration.getScriptFile());
// We can't be sure whether the command is blocking or not, so always make it not blocking
if (pc.getOsType().equals(OsType.WINDOWS)) {
@ -663,7 +659,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
throw new IllegalStateException("iTerm installation not found");
}
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
var a = app.get().toString();
pc.osascriptCommand(String.format(
"""
@ -695,7 +691,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
LocalStore.getShell()
LocalShell.getShell()
.executeSimpleCommand(CommandBuilder.of()
.add("open", "-a")
.addQuoted("Tabby.app")
@ -721,7 +717,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
return;
}
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
pc.osascriptCommand(String.format(
"""
tell application "Warp" to activate
@ -756,7 +752,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
public boolean isAvailable() {
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle();
@ -779,7 +775,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
@Override
public void launch(LaunchConfiguration configuration) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
try (ShellControl pc = LocalShell.getShell()) {
if (!ApplicationHelper.isInPath(pc, executable)) {
throw ErrorEvent.unreportable(
new IOException(

View file

@ -69,7 +69,7 @@ public class TroubleshootComp extends Comp<CompStructure<?>> {
sc.executeSimpleCommand(
ScriptHelper.createDetachCommand(sc, "\"" + script + "\""));
} else {
TerminalHelper.open("XPipe Debug", LocalStore.getShell().command("\"" + script + "\""));
TerminalHelper.open("XPipe Debug", LocalShell.getShell().command("\"" + script + "\""));
}
}
});

View file

@ -15,7 +15,8 @@ public class DataStateProviderImpl extends DataStateProvider {
return;
}
var entry = DataStorage.get().getStoreEntryIfPresent(store);
var entry = DataStorage.get().getStoreEntryIfPresent(store)
.or(() -> DataStorage.get().getStoreEntryInProgressIfPresent(store));
if (entry.isEmpty()) {
return;
}

View file

@ -56,6 +56,8 @@ public abstract class DataStorage {
@Getter
private final List<StorageListener> listeners = new CopyOnWriteArrayList<>();
private final Map<DataStoreEntry, DataStoreEntry> storeEntriesInProgress = new ConcurrentHashMap<>();
protected final ReentrantLock busyIo = new ReentrantLock();
public DataStorage() {
@ -356,6 +358,14 @@ public abstract class DataStorage {
this.listeners.forEach(l -> l.onCategoryAdd(cat));
}
public void addStoreEntryInProgress(@NonNull DataStoreEntry e) {
this.storeEntriesInProgress.put(e, e);
}
public void removeStoreEntryInProgress(@NonNull DataStoreEntry e) {
this.storeEntriesInProgress.remove(e);
}
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) {
var found = storeEntries.get(e);
if (found != null) {
@ -638,6 +648,12 @@ public abstract class DataStorage {
return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found"));
}
public Optional<DataStoreEntry> getStoreEntryInProgressIfPresent(@NonNull DataStore store) {
return storeEntriesInProgress.keySet().stream()
.filter(n -> n.getStore() == store)
.findFirst();
}
public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntriesSet.stream()
.filter(n -> n.getStore() == store

View file

@ -4,8 +4,8 @@ import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation;
@ -79,11 +79,11 @@ public enum XPipeDistributionType {
return PORTABLE;
}
if (!LocalStore.isLocalShellInitialized()) {
if (!LocalShell.isLocalShellInitialized()) {
return UNKNOWN;
}
try (var sc = LocalStore.getShell()) {
try (var sc = LocalShell.getShell()) {
// 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 =

View file

@ -4,7 +4,6 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.FailableSupplier;
import java.io.IOException;
@ -24,7 +23,7 @@ public class ApplicationHelper {
}
public static void executeLocalApplication(Function<ShellControl, String> s, boolean detach) throws Exception {
try (var sc = LocalStore.getShell().start()) {
try (var sc = LocalShell.getShell().start()) {
var cmd = detach ? ScriptHelper.createDetachCommand(sc, s.apply(sc)) : s.apply(sc);
TrackEvent.withDebug("proc", "Executing local application")
.tag("command", cmd)

View file

@ -1,7 +1,6 @@
package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import java.awt.*;
@ -12,9 +11,9 @@ public class DesktopHelper {
public static Path getDesktopDirectory() throws Exception {
if (OsType.getLocal() == OsType.WINDOWS) {
return Path.of(LocalStore.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 = LocalStore.getShell().command("xdg-user-dir DESKTOP").start()) {
try (var cmd = LocalShell.getShell().command("xdg-user-dir DESKTOP").start()) {
var read = cmd.readStdoutDiscardErr();
var exit = cmd.getExitCode();
if (exit == 0) {

View file

@ -1,6 +1,5 @@
package io.xpipe.app.util;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation;
@ -21,7 +20,7 @@ public class DesktopShortcuts {
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.WindowStyle=7; $S.TargetPath = '%%TARGET%%'; $S.Arguments = 'open %s'; $S.Save()"
""",
shortcutTarget, shortcutPath, icon, target);
LocalStore.getShell().executeSimpleCommand(content);
LocalShell.getShell().executeSimpleCommand(content);
}
private static void createLinuxShortcut(String target, String name) throws Exception {
@ -55,7 +54,7 @@ public class DesktopShortcuts {
""",
exec, target);
try (var pc = LocalStore.getShell()) {
try (var pc = LocalShell.getShell()) {
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/MacOS"));
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/Resources"));

View file

@ -3,7 +3,6 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileSystem;
@ -71,7 +70,7 @@ public class FileOpener {
}
public static void openInDefaultApplication(String file) {
try (var pc = LocalStore.getShell().start()) {
try (var pc = LocalShell.getShell().start()) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
pc.executeSimpleCommand("start \"\" \"" + file + "\"");
} else if (pc.getOsType().equals(OsType.LINUX)) {

View file

@ -0,0 +1,40 @@
package io.xpipe.app.util;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import lombok.Getter;
public class LocalShell {
@Getter
private static ShellControlCache localCache;
private static ShellControl local;
private static ShellControl localPowershell;
public static void init() throws Exception {
local = ProcessControlProvider.get().createLocalProcessControl(false).start();
localCache = new ShellControlCache(local);
}
public static ShellControl getLocalPowershell() throws Exception {
if (localPowershell == null) {
localPowershell = ProcessControlProvider.get().createLocalProcessControl(true)
.subShell(ShellDialects.POWERSHELL)
.start();
}
return localPowershell;
}
public static boolean isLocalShellInitialized() {
return local != null;
}
public static ShellControl getShell() {
if (local == null) {
throw new IllegalStateException("Local shell not initialized yet");
}
return local;
}
}

View file

@ -2,7 +2,6 @@ package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.core.store.LocalStore;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Alert;
@ -15,7 +14,7 @@ public class MacOsPermissions {
public static boolean waitForAccessibilityPermissions() throws Exception {
AtomicReference<Alert> alert = new AtomicReference<>();
var state = new SimpleBooleanProperty(true);
try (var pc = LocalStore.getShell().start()) {
try (var pc = LocalShell.getShell().start()) {
while (state.get()) {
// We can't wait in the platform thread, so just return instantly
if (Platform.isFxApplicationThread()) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.process.*;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.SecretValue;
import lombok.SneakyThrows;
@ -36,7 +35,7 @@ public class ScriptHelper {
@SneakyThrows
public static String createLocalExecScript(String content) {
try (var l = LocalStore.getShell().start()) {
try (var l = LocalShell.getShell().start()) {
return createExecScript(l, content);
}
}

View file

@ -0,0 +1,51 @@
package io.xpipe.app.util;
import io.xpipe.core.process.ShellControl;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@Getter
public class ShellControlCache {
private final ShellControl shellControl;
private final Map<String, Boolean> installedApplications = new HashMap<>();
private final Map<String, Object> multiPurposeCache = new HashMap<>();
public ShellControlCache(ShellControl shellControl) {
this.shellControl = shellControl;
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
return (T) multiPurposeCache.get(key);
}
@SuppressWarnings("unchecked")
public <T> T getOrDefault(String key, T val) {
return (T) multiPurposeCache.getOrDefault(key, val);
}
public void set(String key, Object value) {
multiPurposeCache.put(key, value);
}
public void setIfAbsent(String key, Supplier<Object> value) {
multiPurposeCache.computeIfAbsent(key, s -> value.get());
}
public boolean isApplicationInPath(String app) {
if (!installedApplications.containsKey(app)) {
try {
var b = ApplicationHelper.isInPath(shellControl, app);
installedApplications.put(app, b);
} catch (Exception e) {
installedApplications.put(app, false);
}
}
return installedApplications.get(app);
}
}

View file

@ -29,7 +29,7 @@ public class TerminalHelper {
: "";
var cleanTitle = (title != null ? title : entry != null ? entry.getName() : "?");
var adjustedTitle = prefix + cleanTitle;
var file = ScriptHelper.createLocalExecScript(cc.prepareTerminalOpen(new TerminalInitScriptConfig(adjustedTitle, type.shouldClear())));
var file = ScriptHelper.createLocalExecScript(cc.prepareTerminalOpen(new TerminalInitScriptConfig(adjustedTitle, type.shouldClear(), color != null)));
var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? color : null, adjustedTitle, cleanTitle, file);
try {
type.launch(config);

View file

@ -172,7 +172,7 @@ public interface ShellControl extends ProcessControl {
FailableSupplier<SecretValue> getElevationPassword();
default ShellControl subShell(@NonNull ShellDialect type) {
return subShell(p -> type.getOpenCommand(), (sc, command) -> command)
return subShell(p -> type.getOpenCommand(), (sc, command) -> command != null ? command : type.getLoginOpenCommand())
.elevationPassword(getElevationPassword());
}

View file

@ -64,7 +64,7 @@ public interface ShellDialect {
void prepareDumbTerminalCommands(ShellControl sc) throws Exception;
String prepareProperTerminalCommands();
String prepareTerminalEnvironmentCommands();
String appendToPathVariableCommand(String entry);

View file

@ -6,9 +6,10 @@ import lombok.Value;
public class TerminalInitScriptConfig {
public static TerminalInitScriptConfig ofName(String name) {
return new TerminalInitScriptConfig(name, true);
return new TerminalInitScriptConfig(name, true, false);
}
String displayName;
boolean clearScreen;
boolean hasColor;
}

View file

@ -3,7 +3,6 @@ package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.util.JacksonizedValue;
@ -15,44 +14,6 @@ public class LocalStore extends JacksonizedValue implements ShellStore, Stateful
return ShellStoreState.class;
}
private static ShellControl local;
private static ShellControl localPowershell;
private static FileSystem localFileSystem;
public static void init() throws Exception {
local = ProcessControlProvider.get().createLocalProcessControl(false).start();
localFileSystem = new ConnectionFileSystem(ProcessControlProvider.get().createLocalProcessControl(false), new LocalStore());
}
public static ShellControl getLocalPowershell() throws Exception {
if (localPowershell == null) {
localPowershell = ProcessControlProvider.get().createLocalProcessControl(true)
.subShell(ShellDialects.POWERSHELL)
.start();
}
return localPowershell;
}
public static boolean isLocalShellInitialized() {
return local != null;
}
public static ShellControl getShell() {
if (local == null) {
throw new IllegalStateException("Local shell not initialized yet");
}
return local;
}
public static FileSystem getFileSystem() {
if (localFileSystem == null) {
throw new IllegalStateException("Local file system not initialized yet");
}
return localFileSystem;
}
@Override
public ShellControl control() {
var pc = ProcessControlProvider.get().createLocalProcessControl(true);

View file

@ -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.app.util.LocalShell;
import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
@ -32,7 +32,7 @@ public class OpenNativeFileDetailsAction implements LeafAction {
// 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
LocalStore.getLocalPowershell().command(content).notComplex().execute();
LocalShell.getLocalPowershell().command(content).notComplex().execute();
}
case OsType.Linux linux -> {
var dbus = String.format(