mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 09:00:26 +00:00
Rework terminal launching, path caching, and state management
This commit is contained in:
parent
4fda66e7db
commit
2af59af190
27 changed files with 180 additions and 131 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 + "\""));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"));
|
||||
|
||||
|
|
|
@ -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)) {
|
||||
|
|
40
app/src/main/java/io/xpipe/app/util/LocalShell.java
Normal file
40
app/src/main/java/io/xpipe/app/util/LocalShell.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
51
app/src/main/java/io/xpipe/app/util/ShellControlCache.java
Normal file
51
app/src/main/java/io/xpipe/app/util/ShellControlCache.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ public interface ShellDialect {
|
|||
|
||||
void prepareDumbTerminalCommands(ShellControl sc) throws Exception;
|
||||
|
||||
String prepareProperTerminalCommands();
|
||||
String prepareTerminalEnvironmentCommands();
|
||||
|
||||
String appendToPathVariableCommand(String entry);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue