diff --git a/README.md b/README.md
index 8d8812f3f..c85261f99 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-### Next level remote data workflows for everyone
+### A smart connection manager and remote file explorer
-X-Pipe is a connection manager, remote file explorer, and more in an early alpha.
-The core approach is to utilize and integrate well with other tools and workflows,
+X-Pipe takes a fresh spin on the established concept of connection managers.
+The central idea is to utilize and integrate well with your existing tools and workflows,
focusing on augmenting them rather than replacing them.
-X-Pipe is built around existing tools and tries to outsource tasks to them,
-such that you can always use your favorite tools to work with X-Pipe, e.g.
+X-Pipe outsources as many tasks as possible such
+that you can always use your favorite tools to work with X-Pipe, e.g.
text/code editors, terminals, shells, command-line tools and more.
The X-Pipe platform is open source and designed to be extensible, allowing anyone
to implement custom functionality through custom extensions.
diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java
index da949f76b..074e2cc9f 100644
--- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java
+++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemComp.java
@@ -39,7 +39,8 @@ public class OpenFileSystemComp extends SimpleComp {
var path = new SimpleStringProperty(model.getCurrentPath().get());
var pathBar = new TextFieldComp(path, true).createRegion();
path.addListener((observable, oldValue, newValue) -> {
- model.cd(newValue);
+ var changed = model.cd(newValue);
+ changed.ifPresent(path::set);
});
model.getCurrentPath().addListener((observable, oldValue, newValue) -> {
path.set(newValue);
diff --git a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java
index 67999b272..b0409507c 100644
--- a/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java
+++ b/app/src/main/java/io/xpipe/app/browser/OpenFileSystemModel.java
@@ -6,6 +6,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.app.util.ThreadHelper;
+import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.ConnectionFileSystem;
import io.xpipe.core.store.FileSystem;
import io.xpipe.core.store.FileSystemStore;
@@ -18,6 +19,7 @@ import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
@Getter
@@ -51,25 +53,27 @@ final class OpenFileSystemModel {
return new FileSystem.FileEntry(fileSystem, currentPath.get(), Instant.now(), true, false, false, 0);
}
- public void cd(String path) {
+ public Optional cd(String path) {
+ var newPath = FileSystemHelper.normalizeDirectoryPath(this, path);
+ if (!path.equals(newPath)) {
+ return Optional.of(newPath);
+ }
+
ThreadHelper.runFailableAsync(() -> {
try (var ignored = new BusyProperty(busy)) {
cdSync(path);
}
});
+ return Optional.empty();
}
- private boolean cdSync(String path) {
+ private void cdSync(String path) {
path = FileSystemHelper.normalizeDirectoryPath(this, path);
- if (!navigateToSync(path)) {
- return false;
- }
-
+ navigateToSync(path);
filter.setValue(null);
currentPath.set(path);
history.cd(path);
- return true;
}
private boolean navigateToSync(String dir) {
@@ -85,6 +89,7 @@ final class OpenFileSystemModel {
fileList.setAll(newList);
return true;
} catch (Exception e) {
+ fileList.setAll(List.of());
ErrorEvent.fromThrowable(e).handle();
return false;
}
@@ -171,7 +176,7 @@ final class OpenFileSystemModel {
fs.open();
this.fileSystem = fs;
- var current = fs instanceof ConnectionFileSystem connectionFileSystem
+ var current = !(fileSystem instanceof LocalStore) && fs instanceof ConnectionFileSystem connectionFileSystem
? connectionFileSystem
.getShellProcessControl()
.executeStringSimpleCommand(connectionFileSystem
diff --git a/app/src/main/java/io/xpipe/app/core/AppGreetings.java b/app/src/main/java/io/xpipe/app/core/AppGreetings.java
index bd72e073f..7d72858e2 100644
--- a/app/src/main/java/io/xpipe/app/core/AppGreetings.java
+++ b/app/src/main/java/io/xpipe/app/core/AppGreetings.java
@@ -66,7 +66,8 @@ public class AppGreetings {
}
public static void showIfNeeded() {
- if (!AppProperties.get().isImage()) {
+ //TODO
+ if (!AppProperties.get().isImage() || true) {
return;
}
diff --git a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java
index 4d9113ef1..1818355b1 100644
--- a/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java
+++ b/app/src/main/java/io/xpipe/app/issue/SentryErrorHandler.java
@@ -66,7 +66,11 @@ public class SentryErrorHandler {
s.setTag("terminal", Boolean.toString(ee.isTerminal()));
s.setTag("omitted", Boolean.toString(ee.isOmitted()));
- ee.getTrackEvents().forEach(t -> s.addBreadcrumb(toBreadcrumb(t)));
+
+ /*
+ TODO: Ignore breadcrumbs for now
+ */
+ // ee.getTrackEvents().forEach(t -> s.addBreadcrumb(toBreadcrumb(t)));
if (ee.getThrowable() != null) {
if (ee.getDescription() != null
diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java
index 98d1b499e..92b34dde9 100644
--- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java
+++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationType.java
@@ -5,7 +5,6 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
-import io.xpipe.core.store.ShellStore;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -38,7 +37,7 @@ public abstract class ExternalApplicationType implements PrefsChoiceValue {
}
protected Optional getApplicationPath() {
- try (ShellProcessControl pc = ShellStore.createLocal().create().start()) {
+ try (ShellProcessControl pc = LocalStore.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|/Volumes/%s\" | uniq",
diff --git a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java
index f8d2ea092..c94060c6c 100644
--- a/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java
+++ b/app/src/main/java/io/xpipe/app/util/DesktopShortcuts.java
@@ -2,7 +2,6 @@ package io.xpipe.app.util;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
-import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.XPipeInstallation;
import java.nio.file.Files;
@@ -21,7 +20,7 @@ public class DesktopShortcuts {
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.TargetPath = '%%TARGET%%'; $S.Save()"
""",
target, name, icon.toString());
- ShellStore.createLocal().create().executeSimpleCommand(content);
+ LocalStore.getShell().executeSimpleCommand(content);
}
private static void createLinuxShortcut(String target, String name) throws Exception {
diff --git a/app/src/main/java/io/xpipe/app/util/FileOpener.java b/app/src/main/java/io/xpipe/app/util/FileOpener.java
index 5b3e6d69e..cf2553155 100644
--- a/app/src/main/java/io/xpipe/app/util/FileOpener.java
+++ b/app/src/main/java/io/xpipe/app/util/FileOpener.java
@@ -3,9 +3,9 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.impl.FileNames;
+import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.FileSystem;
-import io.xpipe.core.store.ShellStore;
import org.apache.commons.io.FilenameUtils;
import java.nio.file.Path;
@@ -65,7 +65,7 @@ public class FileOpener {
}
public static void openInDefaultApplication(String file) {
- try (var pc = ShellStore.createLocal().create().start()) {
+ try (var pc = LocalStore.getShell().start()) {
if (pc.getOsType().equals(OsType.WINDOWS)) {
pc.executeSimpleCommand("\"" + file + "\"");
} else if (pc.getOsType().equals(OsType.LINUX)) {
diff --git a/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java b/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java
index 07a5fbad0..f109545d6 100644
--- a/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java
+++ b/app/src/main/java/io/xpipe/app/util/MacOsPermissions.java
@@ -2,7 +2,7 @@ package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
-import io.xpipe.core.store.ShellStore;
+import io.xpipe.core.impl.LocalStore;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.control.Alert;
@@ -14,7 +14,7 @@ public class MacOsPermissions {
public static boolean waitForAccessibilityPermissions() throws Exception {
AtomicReference alert = new AtomicReference<>();
var state = new SimpleBooleanProperty(true);
- try (var pc = ShellStore.createLocal().create().start()) {
+ try (var pc = LocalStore.getShell().start()) {
while (state.get()) {
var success = pc.executeBooleanSimpleCommand(
"osascript -e 'tell application \"System Events\" to keystroke \"t\"'");
diff --git a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java
index 83ef6c0da..f64d17d59 100644
--- a/app/src/main/java/io/xpipe/app/util/ScriptHelper.java
+++ b/app/src/main/java/io/xpipe/app/util/ScriptHelper.java
@@ -2,11 +2,11 @@ package io.xpipe.app.util;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.impl.FileNames;
+import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
-import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellDialects;
-import io.xpipe.core.store.ShellStore;
+import io.xpipe.core.process.ShellProcessControl;
import io.xpipe.core.util.SecretValue;
import lombok.SneakyThrows;
@@ -42,7 +42,7 @@ public class ScriptHelper {
@SneakyThrows
public static String createLocalExecScript(String content) {
- try (var l = ShellStore.createLocal().create().start()) {
+ try (var l = LocalStore.getShell().start()) {
return createExecScript(l, content);
}
}
@@ -167,6 +167,19 @@ public class ScriptHelper {
return t.getInitFileOpenCommand(initFile);
}
+ @SneakyThrows
+ public static String getExecScriptFile(ShellProcessControl processControl) {
+ return getExecScriptFile(processControl, processControl.getShellDialect().getScriptFileEnding());
+ }
+
+ @SneakyThrows
+ public static String getExecScriptFile(ShellProcessControl processControl, String fileEnding) {
+ var fileName = "exec-" + getScriptId();
+ var temp = processControl.getTemporaryDirectory();
+ var file = FileNames.join(temp, fileName + "." + fileEnding);
+ return file;
+ }
+
@SneakyThrows
public static String createExecScript(ShellProcessControl processControl, String content) {
var fileName = "exec-" + getScriptId();
diff --git a/core/src/main/java/io/xpipe/core/impl/FileNames.java b/core/src/main/java/io/xpipe/core/impl/FileNames.java
index 77b050bc3..8b833c82f 100644
--- a/core/src/main/java/io/xpipe/core/impl/FileNames.java
+++ b/core/src/main/java/io/xpipe/core/impl/FileNames.java
@@ -18,11 +18,19 @@ public class FileNames {
}
public static String getFileName(String file) {
+ if (file.isEmpty()) {
+ return "";
+ }
+
var split = file.split("[\\\\/]");
if (split.length == 0) {
return "";
}
var components = Arrays.stream(split).filter(s -> !s.isEmpty()).toList();
+ if (components.size() == 0) {
+ return "";
+ }
+
return components.get(components.size() - 1);
}
diff --git a/core/src/main/java/io/xpipe/core/impl/LocalStore.java b/core/src/main/java/io/xpipe/core/impl/LocalStore.java
index 27db73fd4..117177ba6 100644
--- a/core/src/main/java/io/xpipe/core/impl/LocalStore.java
+++ b/core/src/main/java/io/xpipe/core/impl/LocalStore.java
@@ -4,12 +4,12 @@ import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.process.ProcessControlProvider;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.process.ShellProcessControl;
-import io.xpipe.core.store.*;
+import io.xpipe.core.store.ConnectionFileSystem;
+import io.xpipe.core.store.FileSystem;
+import io.xpipe.core.store.FileSystemStore;
+import io.xpipe.core.store.ShellStore;
import io.xpipe.core.util.JacksonizedValue;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
import java.nio.file.Path;
@JsonTypeName("local")
@@ -43,17 +43,6 @@ public class LocalStore extends JacksonizedValue implements ShellStore {
return LocalStore.this;
}
- @Override
- public InputStream openInput(String file) throws Exception {
- var p = wrap(file);
- return Files.newInputStream(p);
- }
-
- @Override
- public OutputStream openOutput(String file) throws Exception {
- var p = wrap(file);
- return Files.newOutputStream(p);
- }
private Path wrap(String file) {
for (var e : System.getenv().entrySet()) {
diff --git a/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java b/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java
index 9b2c4d2ba..f57966891 100644
--- a/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java
+++ b/core/src/main/java/io/xpipe/core/process/CommandProcessControl.java
@@ -49,10 +49,16 @@ public interface CommandProcessControl extends ProcessControl {
public String readOrThrow() throws Exception;
- public default boolean discardAndCheckExit() {
+ public default boolean discardAndCheckExit() throws ProcessOutputException {
try {
discardOrThrow();
return true;
+ } catch (ProcessOutputException ex) {
+ if (ex.isTimeOut()) {
+ throw ex;
+ }
+
+ return false;
} catch (Exception ex) {
return false;
}
diff --git a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java
index 48c9b1b1f..a5c17b857 100644
--- a/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java
+++ b/core/src/main/java/io/xpipe/core/process/ProcessOutputException.java
@@ -25,4 +25,8 @@ public class ProcessOutputException extends Exception {
this.exitCode = exitCode;
this.output = output;
}
+
+ public boolean isTimeOut() {
+ return exitCode == -1;
+ }
}
diff --git a/core/src/main/java/io/xpipe/core/process/ShellDialect.java b/core/src/main/java/io/xpipe/core/process/ShellDialect.java
index 7f81d1c3a..3bcf59299 100644
--- a/core/src/main/java/io/xpipe/core/process/ShellDialect.java
+++ b/core/src/main/java/io/xpipe/core/process/ShellDialect.java
@@ -17,6 +17,14 @@ public interface ShellDialect {
return "cd \"" + directory + "\"";
}
+ default String getPushdCommand(String directory){
+ return "pushd \"" + directory + "\"";
+ }
+
+ default String getPopdCommand(){
+ return "popd";
+ }
+
String getScriptFileEnding();
String addInlineVariablesToCommand(Map variables, String command);
@@ -39,8 +47,6 @@ public interface ShellDialect {
.collect(Collectors.joining(" "));
}
- void disableHistory(ShellProcessControl pc) throws Exception;
-
default String getExitCommand() {
return "exit";
}
diff --git a/core/src/main/java/io/xpipe/core/process/ShellProperties.java b/core/src/main/java/io/xpipe/core/process/ShellProperties.java
new file mode 100644
index 000000000..6b738593a
--- /dev/null
+++ b/core/src/main/java/io/xpipe/core/process/ShellProperties.java
@@ -0,0 +1,10 @@
+package io.xpipe.core.process;
+
+import lombok.Value;
+
+@Value
+public class ShellProperties {
+
+ ShellDialect dialect;
+ boolean tty;
+}
diff --git a/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java
index 20a665107..1146af5b6 100644
--- a/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java
+++ b/ext/base/src/main/java/io/xpipe/ext/base/LocalStoreProvider.java
@@ -8,7 +8,6 @@ import io.xpipe.app.util.XPipeDaemon;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.store.DataStore;
-import io.xpipe.core.store.ShellStore;
import java.util.List;
import java.util.UUID;
@@ -17,7 +16,7 @@ public class LocalStoreProvider implements DataStoreProvider {
@Override
public String queryInformationString(DataStore store, int length) throws Exception {
- try (var pc = ShellStore.createLocal().create().start()) {
+ try (var pc = LocalStore.getShell().start()) {
return OsType.getLocal().determineOperatingSystemName(pc);
}
}