diff --git a/app/src/main/java/io/xpipe/app/core/AppProperties.java b/app/src/main/java/io/xpipe/app/core/AppProperties.java
index aa08eca4a..8150cc878 100644
--- a/app/src/main/java/io/xpipe/app/core/AppProperties.java
+++ b/app/src/main/java/io/xpipe/app/core/AppProperties.java
@@ -60,6 +60,7 @@ public class AppProperties {
ErrorEvent.fromThrowable(e).handle();
}
}
+ var referenceDir = Files.exists(appDir) ? appDir : Path.of(System.getProperty("user.dir"));
image = ModuleHelper.isImage();
fullVersion = Optional.ofNullable(System.getProperty("io.xpipe.app.fullVersion"))
@@ -89,7 +90,7 @@ public class AppProperties {
.map(s -> {
var p = Path.of(s);
if (!p.isAbsolute()) {
- p = appDir.resolve(p);
+ p = referenceDir.resolve(p);
}
return p;
})
diff --git a/app/src/main/resources/io/xpipe/app/resources/misc/api.md b/app/src/main/resources/io/xpipe/app/resources/misc/api.md
index 6363c135f..6e975ee1d 100644
--- a/app/src/main/resources/io/xpipe/app/resources/misc/api.md
+++ b/app/src/main/resources/io/xpipe/app/resources/misc/api.md
@@ -1108,6 +1108,170 @@ string
+## Read the content of a remote file
+
+
+
+`POST /fs/read`
+
+Reads the entire content of a remote file through an active shell session.
+
+> Body parameter
+
+```json
+{
+ "connection": "f0ec68aa-63f5-405c-b178-9a4454556d6b",
+ "path": "/home/user/myfile.txt"
+}
+```
+
+
toProcessCommand(String toExec) {
- return OsType.getLocal().equals(OsType.WINDOWS) ? List.of("cmd", "/c", toExec) : List.of("sh", "-c", toExec);
+ // Having the trailing space is very important to force cmd to not interpret surrounding spaces and removing them
+ return OsType.getLocal().equals(OsType.WINDOWS) ? List.of("cmd", "/c", toExec + " ") : List.of("sh", "-c", toExec);
}
public static Process tryStartCustom() throws Exception {
diff --git a/core/src/main/java/io/xpipe/core/store/DataStoreId.java b/core/src/main/java/io/xpipe/core/store/DataStoreId.java
deleted file mode 100644
index 4c24f0b6a..000000000
--- a/core/src/main/java/io/xpipe/core/store/DataStoreId.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package io.xpipe.core.store;
-
-import lombok.EqualsAndHashCode;
-import lombok.Getter;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * Represents a reference to an XPipe data source.
- * This reference consists out of a collection name and an entry name to allow for better organisation.
- *
- * To allow for a simple usage of data source ids, the collection and entry names are trimmed and
- * converted to lower case names when creating them.
- * The two names are separated by a colon and are therefore not allowed to contain colons themselves.
- *
- * A missing collection name indicates that the data source exists only temporarily.
- *
- * @see #fromString(String)
- */
-@EqualsAndHashCode
-@Getter
-public class DataStoreId {
-
- public static final char SEPARATOR = ':';
-
- private final List names;
-
- public DataStoreId(List names) {
- this.names = names;
- }
-
- /**
- * Creates a new data source id from a collection name and an entry name.
- *
- * @throws IllegalArgumentException if any name is not valid
- */
- public static DataStoreId create(String... names) {
- if (names == null) {
- throw new IllegalArgumentException("Names are null");
- }
-
- if (Arrays.stream(names).anyMatch(s -> s == null)) {
- throw new IllegalArgumentException("Name is null");
- }
-
- if (Arrays.stream(names).anyMatch(s -> s.contains("" + SEPARATOR))) {
- throw new IllegalArgumentException("Separator character " + SEPARATOR + " is not allowed in the names");
- }
-
- if (Arrays.stream(names).anyMatch(s -> s.trim().length() == 0)) {
- throw new IllegalArgumentException("Trimmed entry name is empty");
- }
-
- return new DataStoreId(Arrays.stream(names).toList());
- }
-
- /**
- * Creates a new data source id from a string representation.
- * The string must contain exactly one colon and non-empty names.
- *
- * @param s the string representation, must be not null and fulfill certain requirements
- * @throws IllegalArgumentException if the string is not valid
- */
- public static DataStoreId fromString(String s) {
- if (s == null) {
- throw new IllegalArgumentException("String is null");
- }
-
- var split = s.split(String.valueOf(SEPARATOR), -1);
-
- var names =
- Arrays.stream(split).map(String::trim).map(String::toLowerCase).toList();
- if (names.stream().anyMatch(s1 -> s1.isEmpty())) {
- throw new IllegalArgumentException("Name must not be empty");
- }
-
- return new DataStoreId(names);
- }
-
- @Override
- public String toString() {
- return names.stream().map(String::toLowerCase).collect(Collectors.joining("" + SEPARATOR));
- }
-}
diff --git a/core/src/test/java/io/xpipe/core/test/DataStoreIdTest.java b/core/src/test/java/io/xpipe/core/test/StorePathTest.java
similarity index 60%
rename from core/src/test/java/io/xpipe/core/test/DataStoreIdTest.java
rename to core/src/test/java/io/xpipe/core/test/StorePathTest.java
index e05468ef5..39cc5dafb 100644
--- a/core/src/test/java/io/xpipe/core/test/DataStoreIdTest.java
+++ b/core/src/test/java/io/xpipe/core/test/StorePathTest.java
@@ -1,59 +1,59 @@
package io.xpipe.core.test;
-import io.xpipe.core.store.DataStoreId;
+import io.xpipe.core.store.StorePath;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
-public class DataStoreIdTest {
+public class StorePathTest {
@Test
public void testCreateInvalidParameters() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("a:bc", "abc");
+ StorePath.create("a/bc", "abc");
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create(" \t", "abc");
+ StorePath.create(" \t", "abc");
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("", "abc");
+ StorePath.create("", "abc");
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("abc", null);
+ StorePath.create("abc", null);
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("abc", "a:bc");
+ StorePath.create("abc", "a/bc");
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("abc", " \t");
+ StorePath.create("abc", " \t");
});
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.create("abc", "");
+ StorePath.create("abc", "");
});
}
@Test
public void testFromStringNullParameters() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.fromString(null);
+ StorePath.fromString(null);
});
}
@ParameterizedTest
- @ValueSource(strings = {"abc:", "ab::c", "::abc", "::::", "", " "})
+ @ValueSource(strings = {"abc/", "ab//c", "//abc", "////", "", " "})
public void testFromStringInvalidParameters(String arg) {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- DataStoreId.fromString(arg);
+ StorePath.fromString(arg);
});
}
@Test
public void testFromStringValidParameters() {
- Assertions.assertEquals(DataStoreId.fromString("ab:c"), DataStoreId.fromString(" ab: c "));
- Assertions.assertEquals(DataStoreId.fromString("ab:c"), DataStoreId.fromString(" AB: C "));
- Assertions.assertEquals(DataStoreId.fromString("ab:c"), DataStoreId.fromString("ab:c "));
+ Assertions.assertEquals(StorePath.fromString("ab/c"), StorePath.fromString(" ab/ c "));
+ Assertions.assertEquals(StorePath.fromString("ab/c"), StorePath.fromString(" AB/ C "));
+ Assertions.assertEquals(StorePath.fromString("ab/c"), StorePath.fromString("ab/c "));
}
}
diff --git a/dist/changelogs/10.0.md b/dist/changelogs/10.0.md
index 757ccdcf8..2b8d4fef4 100644
--- a/dist/changelogs/10.0.md
+++ b/dist/changelogs/10.0.md
@@ -8,8 +8,12 @@ To start off, you can query connections based on various filters.
With the matched connections, you can start remote shell sessions for each one and run arbitrary commands in them.
You get the command exit code and output as a response, allowing you to adapt your control flow based on command outputs.
Any kind of passwords and other secrets are automatically provided by XPipe when establishing a shell connection.
+You can also access the file systems via these shell connections to read and write remote files.
-There will be more functionality added to the API in the future, for now this initial implementation is open for feedback.
+There will also be more functionality added to the API in the future.
+
+There already exists a community made XPipe API client for python at https://github.com/coandco/python_xpipe_client.
+It allows you to interact with the API more ergonomically and can also serve as an inspiration of what you can do with the new API.
## Service integration
@@ -63,6 +67,8 @@ The UI has also been streamlined to make common actions and toggles more easily
- Support VMs for tunneling
- Searching for connections has been improved to show children as well
- The welcome screen will now also contain the option to straight up jump to the synchronization settings
+- You can now launch xpipe in another data directory with `xpipe open -d ""`
+- Add option to use double clicks to open connections instead of single clicks
- Add support for foot terminal
- Fix elementary terminal not launching correctly
- Fix kubernetes not elevating correctly for non-default contexts
diff --git a/version b/version
index 5c933c446..15fc8617c 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-10.0-11
+10.0-12