diff --git a/README.md b/README.md index 8d8812f3f..c85261f99 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ drawing -### 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); } }