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; 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.ShellControl;
import io.xpipe.core.process.ShellDialect; import io.xpipe.core.process.ShellDialect;
import lombok.Getter; import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
@Getter @Getter
public class OpenFileSystemCache { public class OpenFileSystemCache extends ShellControlCache {
private final OpenFileSystemModel model; private final OpenFileSystemModel model;
private final Map<String, Boolean> installedApplications = new HashMap<>(); private final String username;
private final Map<String, Object> multiPurposeCache = new HashMap<>();
private String username;
public OpenFileSystemCache(OpenFileSystemModel model) { public OpenFileSystemCache(OpenFileSystemModel model) throws Exception {
super(model.getFileSystem().getShell().orElseThrow());
this.model = model; 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(); ShellControl sc = model.getFileSystem().getShell().get();
ShellDialect d = sc.getShellDialect(); ShellDialect d = sc.getShellDialect();
username = d.printUsernameCommand(sc).readStdoutOrThrow(); username = d.printUsernameCommand(sc).readStdoutOrThrow();
@ -38,18 +23,4 @@ public class OpenFileSystemCache {
public boolean isRoot() { public boolean isRoot() {
return username.equals("root"); 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 BooleanProperty busy = new SimpleBooleanProperty();
private final BrowserModel browserModel; private final BrowserModel browserModel;
private OpenFileSystemSavedState savedState; private OpenFileSystemSavedState savedState;
private final OpenFileSystemCache cache = new OpenFileSystemCache(this); private OpenFileSystemCache cache;
private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>(); private final Property<ModalOverlayComp.OverlayContent> overlay = new SimpleObjectProperty<>();
private final BooleanProperty inOverview = new SimpleBooleanProperty(); private final BooleanProperty inOverview = new SimpleBooleanProperty();
private final String name; private final String name;
@ -375,7 +375,7 @@ public final class OpenFileSystemModel {
.map(shellControl -> shellControl.hasLocalSystemAccess()) .map(shellControl -> shellControl.hasLocalSystemAccess())
.orElse(false); .orElse(false);
this.cache.init(); this.cache = new OpenFileSystemCache(this);
for (BrowserAction b : BrowserAction.ALL) { for (BrowserAction b : BrowserAction.ALL) {
b.init(this); b.init(this);
} }

View file

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

View file

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

View file

@ -4,10 +4,10 @@ import io.xpipe.app.core.*;
import io.xpipe.app.ext.DataStoreProviders; import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.*; import io.xpipe.app.issue.*;
import io.xpipe.app.launcher.LauncherCommand; import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.PlatformState; import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper; import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeSession; import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.FailableRunnable; import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.XPipeDaemonMode; import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
@ -184,7 +184,7 @@ public abstract class OperationMode {
public static void restart() { public static void restart() {
OperationMode.executeAfterShutdown(() -> { OperationMode.executeAfterShutdown(() -> {
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(XPipeInstallation.getLocalDefaultInstallationBasePath(), XPipeDaemonMode.GUI, ""); 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<>(); 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 { class Loader implements ModuleLayerLoader {
@Override @Override
@ -51,6 +61,9 @@ public interface ActionProvider {
void execute() throws Exception; void execute() throws Exception;
} }
default void init() {
}
default String getId() { default String getId() {
return null; return null;
} }

View file

@ -2,7 +2,7 @@ package io.xpipe.app.prefs;
import io.xpipe.app.ext.PrefsChoiceValue; import io.xpipe.app.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent; 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.OsType;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects; import io.xpipe.core.process.ShellDialects;
@ -44,7 +44,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
} }
protected Optional<Path> getApplicationPath() { protected Optional<Path> getApplicationPath() {
try (ShellControl pc = LocalStore.getShell().start()) { try (ShellControl pc = LocalShell.getShell().start()) {
try (var c = pc.command(String.format( try (var c = pc.command(String.format(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister " "/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", + "-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() { public boolean isAvailable() {
try (ShellControl pc = LocalStore.getShell()) { try (ShellControl pc = LocalShell.getShell()) {
return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable)); return pc.executeSimpleBooleanCommand(pc.getShellDialect().getWhichCommand(executable));
} catch (Exception e) { } catch (Exception e) {
ErrorEvent.fromThrowable(e).omit().handle(); ErrorEvent.fromThrowable(e).omit().handle();
@ -122,7 +122,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
protected Optional<Path> determineFromPath() { protected Optional<Path> determineFromPath() {
// Try to locate if it is in the Path // Try to locate if it is in the Path
try (var cc = LocalStore.getShell() try (var cc = LocalShell.getShell()
.command(ShellDialects.getPlatformDefault().getWhichCommand(executable)) .command(ShellDialects.getPlatformDefault().getWhichCommand(executable))
.start()) { .start()) {
var out = cc.readStdoutDiscardErr(); 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.ext.PrefsChoiceValue;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.ApplicationHelper; import io.xpipe.app.util.ApplicationHelper;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.WindowsRegistry; import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.CommandBuilder; import io.xpipe.core.process.CommandBuilder;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.store.LocalStore;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
@ -152,7 +152,7 @@ public interface ExternalEditorType extends PrefsChoiceValue {
@Override @Override
public void launch(Path file) throws Exception { 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 @Override

View file

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

View file

@ -69,7 +69,7 @@ public class TroubleshootComp extends Comp<CompStructure<?>> {
sc.executeSimpleCommand( sc.executeSimpleCommand(
ScriptHelper.createDetachCommand(sc, "\"" + script + "\"")); ScriptHelper.createDetachCommand(sc, "\"" + script + "\""));
} else { } 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; return;
} }
var entry = DataStorage.get().getStoreEntryIfPresent(store); var entry = DataStorage.get().getStoreEntryIfPresent(store)
.or(() -> DataStorage.get().getStoreEntryInProgressIfPresent(store));
if (entry.isEmpty()) { if (entry.isEmpty()) {
return; return;
} }

View file

@ -56,6 +56,8 @@ public abstract class DataStorage {
@Getter @Getter
private final List<StorageListener> listeners = new CopyOnWriteArrayList<>(); private final List<StorageListener> listeners = new CopyOnWriteArrayList<>();
private final Map<DataStoreEntry, DataStoreEntry> storeEntriesInProgress = new ConcurrentHashMap<>();
protected final ReentrantLock busyIo = new ReentrantLock(); protected final ReentrantLock busyIo = new ReentrantLock();
public DataStorage() { public DataStorage() {
@ -356,6 +358,14 @@ public abstract class DataStorage {
this.listeners.forEach(l -> l.onCategoryAdd(cat)); 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) { public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) {
var found = storeEntries.get(e); var found = storeEntries.get(e);
if (found != null) { if (found != null) {
@ -638,6 +648,12 @@ public abstract class DataStorage {
return getStoreEntryIfPresent(store).orElseThrow(() -> new IllegalArgumentException("Store not found")); 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) { public Optional<DataStoreEntry> getStoreEntryIfPresent(@NonNull DataStore store) {
return storeEntriesSet.stream() return storeEntriesSet.stream()
.filter(n -> n.getStore() == store .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.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.LocalShell;
import io.xpipe.app.util.XPipeSession; import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.ModuleHelper; import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation; import io.xpipe.core.util.XPipeInstallation;
@ -79,11 +79,11 @@ public enum XPipeDistributionType {
return PORTABLE; return PORTABLE;
} }
if (!LocalStore.isLocalShellInitialized()) { if (!LocalShell.isLocalShellInitialized()) {
return UNKNOWN; 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 // In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the production behavior
if (OsType.getLocal().equals(OsType.WINDOWS)) { if (OsType.getLocal().equals(OsType.WINDOWS)) {
try (var chocoOut = 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.issue.TrackEvent;
import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.FailableSupplier; import io.xpipe.core.util.FailableSupplier;
import java.io.IOException; import java.io.IOException;
@ -24,7 +23,7 @@ public class ApplicationHelper {
} }
public static void executeLocalApplication(Function<ShellControl, String> s, boolean detach) throws Exception { 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); var cmd = detach ? ScriptHelper.createDetachCommand(sc, s.apply(sc)) : s.apply(sc);
TrackEvent.withDebug("proc", "Executing local application") TrackEvent.withDebug("proc", "Executing local application")
.tag("command", cmd) .tag("command", cmd)

View file

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

View file

@ -1,6 +1,5 @@
package io.xpipe.app.util; package io.xpipe.app.util;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.util.XPipeInstallation; 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()" %%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); shortcutTarget, shortcutPath, icon, target);
LocalStore.getShell().executeSimpleCommand(content); LocalShell.getShell().executeSimpleCommand(content);
} }
private static void createLinuxShortcut(String target, String name) throws Exception { private static void createLinuxShortcut(String target, String name) throws Exception {
@ -55,7 +54,7 @@ public class DesktopShortcuts {
""", """,
exec, target); 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/MacOS"));
pc.executeSimpleCommand(pc.getShellDialect().getMkdirsCommand(base + "/Contents/Resources")); 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.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.CommandControl; import io.xpipe.core.process.CommandControl;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileSystem; import io.xpipe.core.store.FileSystem;
@ -71,7 +70,7 @@ public class FileOpener {
} }
public static void openInDefaultApplication(String file) { public static void openInDefaultApplication(String file) {
try (var pc = LocalStore.getShell().start()) { try (var pc = LocalShell.getShell().start()) {
if (pc.getOsType().equals(OsType.WINDOWS)) { if (pc.getOsType().equals(OsType.WINDOWS)) {
pc.executeSimpleCommand("start \"\" \"" + file + "\""); pc.executeSimpleCommand("start \"\" \"" + file + "\"");
} else if (pc.getOsType().equals(OsType.LINUX)) { } 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.AppI18n;
import io.xpipe.app.core.AppWindowHelper; import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.core.store.LocalStore;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
@ -15,7 +14,7 @@ public class MacOsPermissions {
public static boolean waitForAccessibilityPermissions() throws Exception { public static boolean waitForAccessibilityPermissions() throws Exception {
AtomicReference<Alert> alert = new AtomicReference<>(); AtomicReference<Alert> alert = new AtomicReference<>();
var state = new SimpleBooleanProperty(true); var state = new SimpleBooleanProperty(true);
try (var pc = LocalStore.getShell().start()) { try (var pc = LocalShell.getShell().start()) {
while (state.get()) { while (state.get()) {
// We can't wait in the platform thread, so just return instantly // We can't wait in the platform thread, so just return instantly
if (Platform.isFxApplicationThread()) { if (Platform.isFxApplicationThread()) {

View file

@ -3,7 +3,6 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.process.*; import io.xpipe.core.process.*;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.util.SecretValue; import io.xpipe.core.util.SecretValue;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -36,7 +35,7 @@ public class ScriptHelper {
@SneakyThrows @SneakyThrows
public static String createLocalExecScript(String content) { public static String createLocalExecScript(String content) {
try (var l = LocalStore.getShell().start()) { try (var l = LocalShell.getShell().start()) {
return createExecScript(l, content); 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 cleanTitle = (title != null ? title : entry != null ? entry.getName() : "?");
var adjustedTitle = prefix + cleanTitle; 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); var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? color : null, adjustedTitle, cleanTitle, file);
try { try {
type.launch(config); type.launch(config);

View file

@ -172,7 +172,7 @@ public interface ShellControl extends ProcessControl {
FailableSupplier<SecretValue> getElevationPassword(); FailableSupplier<SecretValue> getElevationPassword();
default ShellControl subShell(@NonNull ShellDialect type) { 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()); .elevationPassword(getElevationPassword());
} }

View file

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

View file

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

View file

@ -3,7 +3,6 @@ package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider; import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellControl; import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellStoreState; import io.xpipe.core.process.ShellStoreState;
import io.xpipe.core.util.JacksonizedValue; import io.xpipe.core.util.JacksonizedValue;
@ -15,44 +14,6 @@ public class LocalStore extends JacksonizedValue implements ShellStore, Stateful
return ShellStoreState.class; 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 @Override
public ShellControl control() { public ShellControl control() {
var pc = ProcessControlProvider.get().createLocalProcessControl(true); 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.BrowserEntry;
import io.xpipe.app.browser.OpenFileSystemModel; import io.xpipe.app.browser.OpenFileSystemModel;
import io.xpipe.app.browser.action.LeafAction; import io.xpipe.app.browser.action.LeafAction;
import io.xpipe.app.util.LocalShell;
import io.xpipe.core.store.FileNames; import io.xpipe.core.store.FileNames;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType; import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl; 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 // The Windows shell invoke verb functionality behaves kinda weirdly and only shows the window as
// long as the parent process is running. // long as the parent process is running.
// So let's keep one process running // So let's keep one process running
LocalStore.getLocalPowershell().command(content).notComplex().execute(); LocalShell.getLocalPowershell().command(content).notComplex().execute();
} }
case OsType.Linux linux -> { case OsType.Linux linux -> {
var dbus = String.format( var dbus = String.format(