This commit is contained in:
Christopher Schnick 2022-08-12 17:26:01 +02:00
parent 0f37600c96
commit b1a75f6d21
56 changed files with 673 additions and 285 deletions

2
.gitignore vendored
View file

@ -4,3 +4,5 @@ build/
dev.properties dev.properties
extensions.txt extensions.txt
local/ local/
.vscode
bin

View file

@ -7,6 +7,7 @@ plugins {
apply from: "$rootDir/deps/java.gradle" apply from: "$rootDir/deps/java.gradle"
apply from: "$rootDir/deps/junit.gradle" apply from: "$rootDir/deps/junit.gradle"
apply from: "$rootDir/deps/jackson.gradle"
apply from: 'publish.gradle' apply from: 'publish.gradle'
apply from: "$rootDir/deps/publish-base.gradle" apply from: "$rootDir/deps/publish-base.gradle"
@ -19,18 +20,18 @@ repositories {
} }
test { test {
enabled = false enabled = true
} }
dependencies { dependencies {
// Fix warnings about missing annotations
compileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
testCompileOnly group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.13.0"
implementation project(':core') implementation project(':core')
implementation project(':beacon') implementation project(':beacon')
} }
configurations {
testImplementation.extendsFrom(dep)
}
def canTestWithDev = findProject(':app') != null def canTestWithDev = findProject(':app') != null
def home = System.getenv('XPIPE_HOME') def home = System.getenv('XPIPE_HOME')
String daemonCommand = canTestWithDev ? String daemonCommand = canTestWithDev ?
@ -52,7 +53,7 @@ test {
" -Dio.xpipe.app.logLevel=trace" " -Dio.xpipe.app.logLevel=trace"
// API properties // API properties
// systemProperty 'io.xpipe.beacon.debugOutput', "true" systemProperty 'io.xpipe.beacon.debugOutput', "true"
systemProperty 'io.xpipe.beacon.debugExecOutput', "true" systemProperty 'io.xpipe.beacon.debugExecOutput', "true"
systemProperty "io.xpipe.beacon.port", "21722" systemProperty "io.xpipe.beacon.port", "21722"
} }

View file

@ -1,7 +1,9 @@
package io.xpipe.api; package io.xpipe.api;
import io.xpipe.api.impl.DataSourceImpl; import io.xpipe.api.impl.DataSourceImpl;
import io.xpipe.core.source.*; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference;
import io.xpipe.core.source.DataSourceType;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -9,7 +11,6 @@ import java.io.UncheckedIOException;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map;
/** /**
* Represents a reference to a data source that is managed by X-Pipe. * Represents a reference to a data source that is managed by X-Pipe.
@ -85,36 +86,36 @@ public interface DataSource {
} }
/** /**
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source. * Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
*/ */
public static DataSource createAnonymous(String type, Map<String,String> config, Path path) { public static DataSource createAnonymous(String type, Path path) {
return create(null, type, config, path); return create(null, type, path);
} }
/** /**
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)}. * Wrapper for {@link #create(DataSourceId, String, InputStream)}.
*/ */
public static DataSource create(DataSourceId id, String type, Map<String,String> config, Path path) { public static DataSource create(DataSourceId id, String type, Path path) {
try (var in = Files.newInputStream(path)) { try (var in = Files.newInputStream(path)) {
return create(id, type, config, in); return create(id, type, in);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
/** /**
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source. * Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
*/ */
public static DataSource createAnonymous(String type, Map<String,String> config, URL url) { public static DataSource createAnonymous(String type, URL url) {
return create(null, type, config, url); return create(null, type, url);
} }
/** /**
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)}. * Wrapper for {@link #create(DataSourceId, String, InputStream)}.
*/ */
public static DataSource create(DataSourceId id, String type, Map<String,String> config, URL url) { public static DataSource create(DataSourceId id, String type, URL url) {
try (var in = url.openStream()) { try (var in = url.openStream()) {
return create(id, type, config, in); return create(id, type, in);
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
@ -122,10 +123,10 @@ public interface DataSource {
/** /**
* Wrapper for {@link #create(DataSourceId, String, Map, InputStream)} that creates an anonymous data source. * Wrapper for {@link #create(DataSourceId, String, InputStream)} that creates an anonymous data source.
*/ */
public static DataSource createAnonymous(String type, Map<String,String> config, InputStream in) { public static DataSource createAnonymous(String type, InputStream in) {
return create(null, type, config, in); return create(null, type, in);
} }
/** /**
@ -133,12 +134,11 @@ public interface DataSource {
* *
* @param id the data source id * @param id the data source id
* @param type the data source type * @param type the data source type
* @param config additional configuration options for the specific data source type
* @param in the input stream to read * @param in the input stream to read
* @return a {@link DataSource} instances that can be used to access the underlying data * @return a {@link DataSource} instances that can be used to access the underlying data
*/ */
public static DataSource create(DataSourceId id, String type, Map<String,String> config, InputStream in) { public static DataSource create(DataSourceId id, String type, InputStream in) {
return DataSourceImpl.create(id, type, config, in); return DataSourceImpl.create(id, type, in);
} }
/** /**

View file

@ -1,6 +1,8 @@
package io.xpipe.api.connector; package io.xpipe.api.connector;
import io.xpipe.beacon.*; import io.xpipe.beacon.*;
import io.xpipe.beacon.exchange.cli.DialogExchange;
import io.xpipe.core.dialog.DialogReference;
import io.xpipe.core.util.JacksonHelper; import io.xpipe.core.util.JacksonHelper;
import java.util.Optional; import java.util.Optional;
@ -13,6 +15,22 @@ public final class XPipeConnection extends BeaconConnection {
return con; return con;
} }
public static void finishDialog(DialogReference reference) {
try (var con = new XPipeConnection()) {
con.constructSocket();
while (true) {
DialogExchange.Response response = con.performSimpleExchange(DialogExchange.Request.builder().dialogKey(reference.getDialogId()).build());
if (response.getElement() == null) {
break;
}
}
} catch (BeaconException e) {
throw e;
} catch (Exception e) {
throw new BeaconException(e);
}
}
public static void execute(Handler handler) { public static void execute(Handler handler) {
try (var con = new XPipeConnection()) { try (var con = new XPipeConnection()) {
con.constructSocket(); con.constructSocket();
@ -74,7 +92,7 @@ public final class XPipeConnection extends BeaconConnection {
} }
public static Optional<BeaconClient> waitForStartup() { public static Optional<BeaconClient> waitForStartup() {
for (int i = 0; i < 40; i++) { for (int i = 0; i < 80; i++) {
try { try {
Thread.sleep(500); Thread.sleep(500);
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {

View file

@ -10,7 +10,6 @@ import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map;
public abstract class DataSourceImpl implements DataSource { public abstract class DataSourceImpl implements DataSource {
@ -19,29 +18,30 @@ public abstract class DataSourceImpl implements DataSource {
var req = QueryDataSourceExchange.Request.builder().ref(ds).build(); var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
QueryDataSourceExchange.Response res = con.performSimpleExchange(req); QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
var config = new DataSourceConfig(res.getProvider(), res.getConfig()); var config = new DataSourceConfig(res.getProvider(), res.getConfig());
switch (res.getInfo().getType()) { return switch (res.getInfo().getType()) {
case TABLE -> { case TABLE -> {
var data = res.getInfo().asTable(); var data = res.getInfo().asTable();
return new DataTableImpl(res.getId(), config, data); yield new DataTableImpl(res.getId(), config, data);
} }
case STRUCTURE -> { case STRUCTURE -> {
var info = res.getInfo().asStructure(); var info = res.getInfo().asStructure();
return new DataStructureImpl(res.getId(), config, info); yield new DataStructureImpl(res.getId(), config, info);
} }
case TEXT -> { case TEXT -> {
var info = res.getInfo().asText(); var info = res.getInfo().asText();
return new DataTextImpl(res.getId(), config, info); yield new DataTextImpl(res.getId(), config, info);
} }
case RAW -> { case RAW -> {
var info = res.getInfo().asRaw(); var info = res.getInfo().asRaw();
return new DataRawImpl(res.getId(), config, info); yield new DataRawImpl(res.getId(), config, info);
} }
} case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getInfo().getType());
throw new AssertionError(); default -> throw new IllegalArgumentException("Unexpected value: " + res.getInfo().getType());
};
}); });
} }
public static DataSource create(DataSourceId id, String type, Map<String,String> config, InputStream in) { public static DataSource create(DataSourceId id, String type, InputStream in) {
var res = XPipeConnection.execute(con -> { var res = XPipeConnection.execute(con -> {
var req = StoreStreamExchange.Request.builder().build(); var req = StoreStreamExchange.Request.builder().build();
StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out)); StoreStreamExchange.Response r = con.performOutputExchange(req, out -> in.transferTo(out));
@ -53,6 +53,8 @@ public abstract class DataSourceImpl implements DataSource {
var startReq = ReadExchange.Request.builder() var startReq = ReadExchange.Request.builder()
.provider(type) .provider(type)
.store(store) .store(store)
.target(id)
.configureAll(false)
.build(); .build();
var startRes = XPipeConnection.execute(con -> { var startRes = XPipeConnection.execute(con -> {
ReadExchange.Response r = con.performSimpleExchange(startReq); ReadExchange.Response r = con.performSimpleExchange(startReq);
@ -60,13 +62,8 @@ public abstract class DataSourceImpl implements DataSource {
}); });
var configInstance = startRes.getConfig(); var configInstance = startRes.getConfig();
//TODO XPipeConnection.finishDialog(configInstance);
// configInstance.getConfigInstance().getCurrentValues().putAll(config);
// var endReq = ReadExecuteExchange.Request.builder()
// .target(id).dataStore(store).config(configInstance).build();
// XPipeConnection.execute(con -> {
// con.performSimpleExchange(endReq);
// });
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest(); var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
return get(ref); return get(ref);
} }

View file

@ -5,6 +5,8 @@ import io.xpipe.api.DataTable;
import io.xpipe.api.DataTableAccumulator; import io.xpipe.api.DataTableAccumulator;
import io.xpipe.api.connector.XPipeConnection; import io.xpipe.api.connector.XPipeConnection;
import io.xpipe.api.util.TypeDescriptor; import io.xpipe.api.util.TypeDescriptor;
import io.xpipe.beacon.BeaconException;
import io.xpipe.beacon.exchange.ReadExchange;
import io.xpipe.beacon.exchange.StoreStreamExchange; import io.xpipe.beacon.exchange.StoreStreamExchange;
import io.xpipe.core.data.node.DataStructureNode; import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.data.node.DataStructureNodeAcceptor; import io.xpipe.core.data.node.DataStructureNodeAcceptor;
@ -14,6 +16,7 @@ import io.xpipe.core.data.typed.TypedDataStreamWriter;
import io.xpipe.core.source.DataSourceId; import io.xpipe.core.source.DataSourceId;
import io.xpipe.core.source.DataSourceReference; import io.xpipe.core.source.DataSourceReference;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -23,25 +26,35 @@ public class DataTableAccumulatorImpl implements DataTableAccumulator {
private final TupleType type; private final TupleType type;
private int rows; private int rows;
private TupleType writtenDescriptor; private TupleType writtenDescriptor;
private OutputStream bodyOutput;
public DataTableAccumulatorImpl(TupleType type) { public DataTableAccumulatorImpl(TupleType type) {
this.type = type; this.type = type;
connection = XPipeConnection.open(); connection = XPipeConnection.open();
connection.sendRequest(StoreStreamExchange.Request.builder().build()); connection.sendRequest(StoreStreamExchange.Request.builder().build());
connection.sendBody(); bodyOutput = connection.sendBody();
} }
@Override @Override
public synchronized DataTable finish(DataSourceId id) { public synchronized DataTable finish(DataSourceId id) {
connection.withOutputStream(OutputStream::close); try {
bodyOutput.close();
} catch (IOException e) {
throw new BeaconException(e);
}
StoreStreamExchange.Response res = connection.receiveResponse(); StoreStreamExchange.Response res = connection.receiveResponse();
connection.close(); connection.close();
// var req = ReadExecuteExchange.Request.builder() var req = ReadExchange.Request.builder()
// .target(id).dataStore(res.getStore()).build(); .target(id).store(res.getStore()).provider("xpbt").configureAll(false).build();
// XPipeConnection.execute(con -> { ReadExchange.Response response = XPipeConnection.execute(con -> {
// con.performSimpleExchange(req); return con.performSimpleExchange(req);
// }); });
var configInstance = response.getConfig();
XPipeConnection.finishDialog(configInstance);
return DataSource.get(DataSourceReference.id(id)).asTable(); return DataSource.get(DataSourceReference.id(id)).asTable();
} }

View file

@ -5,13 +5,11 @@ import io.xpipe.core.source.DataSourceId;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map;
public class DataTableTest extends DaemonControl { public class DataTableTest extends DaemonControl {
@BeforeAll @BeforeAll
public static void setupStorage() throws Exception { public static void setupStorage() throws Exception {
DataSource.create(DataSourceId.fromString(":usernames"), "csv", Map.of(), DataTableTest.class.getResource("username.csv")); DataSource.create(DataSourceId.fromString(":usernames"), "csv", DataTableTest.class.getResource("username.csv"));
} }
@Test @Test

View file

@ -15,8 +15,8 @@ module io.xpipe.beacon {
opens io.xpipe.beacon.exchange.data; opens io.xpipe.beacon.exchange.data;
opens io.xpipe.beacon.exchange.cli; opens io.xpipe.beacon.exchange.cli;
requires com.fasterxml.jackson.core; requires static com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind; requires static com.fasterxml.jackson.databind;
requires transitive io.xpipe.core; requires transitive io.xpipe.core;
requires static lombok; requires static lombok;

View file

@ -3,9 +3,7 @@ plugins {
} }
if(project == rootProject) { if(project == rootProject) {
plugins { apply plugin: "io.codearte.nexus-staging"
id "io.codearte.nexus-staging" version "0.30.0"
}
} }
version file('misc/version').text version file('misc/version').text

View file

@ -24,3 +24,7 @@ archivesBaseName = 'core'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies{
compileOnly 'org.apache.commons:commons-exec:1.3'
}

View file

@ -86,7 +86,7 @@ public abstract class DataStructureNode implements Iterable<DataStructureNode> {
} }
public boolean isNull() { public boolean isNull() {
throw unsupported("null check"); return false;
} }
public final ValueNode asValue() { public final ValueNode asValue() {

View file

@ -106,4 +106,10 @@ public abstract class ValueNode extends DataStructureNode {
} }
public abstract byte[] getRawData(); public abstract byte[] getRawData();
@Override
public boolean isNull() {
return getRawData() == null;
}
} }

View file

@ -10,12 +10,12 @@ import java.util.function.Supplier;
/** /**
* A Dialog is a sequence of questions and answers. * A Dialog is a sequence of questions and answers.
* * <p>
* The dialogue API is only used for the command line interface. * The dialogue API is only used for the command line interface.
* Therefore, the actual implementation is handled by the command line component. * Therefore, the actual implementation is handled by the command line component.
* This API provides a way of creating server-side dialogues which makes * This API provides a way of creating server-side dialogues which makes
* it possible to create extensions that provide a commandline configuration component. * it possible to create extensions that provide a commandline configuration component.
* * <p>
* When a Dialog is completed, it can also be optionally evaluated to a value, which can be queried by calling {@link #getResult()}. * When a Dialog is completed, it can also be optionally evaluated to a value, which can be queried by calling {@link #getResult()}.
* The evaluation function can be set with {@link #evaluateTo(Supplier)}. * The evaluation function can be set with {@link #evaluateTo(Supplier)}.
* Alternatively, a dialogue can also copy the evaluation function of another dialogue with {@link #evaluateTo(Dialog)}. * Alternatively, a dialogue can also copy the evaluation function of another dialogue with {@link #evaluateTo(Dialog)}.
@ -197,7 +197,8 @@ public abstract class Dialog {
if (currentElement == null) { if (currentElement == null) {
DialogElement next = null; DialogElement next = null;
while (current < ds.length - 1 && (next = ds[++current].start()) == null) { while (current < ds.length - 1 && (next = ds[++current].start()) == null) {
}; }
;
return next; return next;
} }
@ -424,7 +425,7 @@ public abstract class Dialog {
public abstract DialogElement start() throws Exception; public abstract DialogElement start() throws Exception;
public Dialog evaluateTo(Dialog d) { public Dialog evaluateTo(Dialog d) {
evaluation = () -> d.evaluation.get(); evaluation = () -> d.evaluation != null ? d.evaluation.get() : null;
return this; return this;
} }

View file

@ -32,6 +32,11 @@ public class DataSourceId {
this.entryName = entryName; this.entryName = entryName;
} }
public static String cleanString(String input) {
var replaced = input.replaceAll(":", "");
return replaced.length() == 0 ? "-" : replaced;
}
/** /**
* Creates a new data source id from a collection name and an entry name. * Creates a new data source id from a collection name and an entry name.
* *

View file

@ -41,12 +41,16 @@ public interface DataStore {
/** /**
* Creates a display string of this store. * Creates a display string of this store.
* This can be a multiline string.
*/ */
default String toDisplay() { default String toSummaryString() {
return null; return null;
} }
default String queryInformationString() throws Exception {
return null;
}
/** /**
* Casts this instance to the required type without checking whether a cast is possible. * Casts this instance to the required type without checking whether a cast is possible.
*/ */

View file

@ -0,0 +1,27 @@
package io.xpipe.core.store;
public class DataStoreFormatter {
public static String ellipsis(String input, int length) {
var end = Math.min(input.length(), length);
if (end < input.length()) {
return input.substring(0, end) + "...";
}
return input;
}
public static String specialFormatHostName(String input) {
if (input.contains(":")) {
input = input.split(":")[0];
}
if (input.endsWith(".rds.amazonaws.com")) {
var split = input.split("\\.");
var name = split[0];
var region = split[2];
return String.format("RDS %s @ %s", name, region);
}
return null;
}
}

View file

@ -16,14 +16,14 @@ import java.nio.file.Path;
public class FileStore implements StreamDataStore, FilenameStore { public class FileStore implements StreamDataStore, FilenameStore {
public static FileStore local(Path p) { public static FileStore local(Path p) {
return new FileStore(MachineFileStore.local(), p.toString()); return new FileStore(new LocalStore(), p.toString());
} }
/** /**
* Creates a file store for a file that is local to the callers machine. * Creates a file store for a file that is local to the callers machine.
*/ */
public static FileStore local(String p) { public static FileStore local(String p) {
return new FileStore(MachineFileStore.local(), p); return new FileStore(new LocalStore(), p);
} }
MachineFileStore machine; MachineFileStore machine;
@ -38,7 +38,7 @@ public class FileStore implements StreamDataStore, FilenameStore {
@Override @Override
public void validate() throws Exception { public void validate() throws Exception {
if (!machine.exists(file)) { if (!machine.exists(file)) {
throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toDisplay()); throw new IllegalStateException("File " + file + " could not be found on machine " + machine.toSummaryString());
} }
} }
@ -58,8 +58,8 @@ public class FileStore implements StreamDataStore, FilenameStore {
} }
@Override @Override
public String toDisplay() { public String toSummaryString() {
return file + "@" + machine.toDisplay(); return file + "@" + machine.toSummaryString();
} }
@Override @Override
@ -69,6 +69,10 @@ public class FileStore implements StreamDataStore, FilenameStore {
@Override @Override
public String getFileName() { public String getFileName() {
return file; var split = file.split("[\\\\/]");
if (split.length == 0) {
return "";
}
return split[split.length - 1];
} }
} }

View file

@ -37,7 +37,7 @@ public class InMemoryStore implements StreamDataStore {
} }
@Override @Override
public String toDisplay() { public String toSummaryString() {
return "inMemory"; return "inMemory";
} }
} }

View file

@ -1,33 +1,39 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.util.Secret;
import lombok.Value; import lombok.Value;
import java.io.IOException; import java.io.*;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@JsonTypeName("local") @JsonTypeName("local")
@Value @Value
public class LocalStore implements ShellProcessStore { public class LocalStore extends StandardShellStore implements MachineFileStore {
@Override
public boolean isLocal() {
return true;
}
static class LocalProcessControl extends ProcessControl { static class LocalProcessControl extends ProcessControl {
private final InputStream input; private final List<Secret> input;
private final ProcessBuilder builder; private final ProcessBuilder builder;
private final Integer timeout;
private Process process; private Process process;
LocalProcessControl(InputStream input, List<String> cmd) { LocalProcessControl(List<Secret> input, List<String> cmd, Integer timeout) {
this.input = input; this.input = input;
this.timeout = timeout;
var l = new ArrayList<String>(); var l = new ArrayList<String>();
l.add("cmd"); l.add("cmd");
l.add("/c"); l.add("/c");
@ -35,13 +41,19 @@ public class LocalStore implements ShellProcessStore {
builder = new ProcessBuilder(l); builder = new ProcessBuilder(l);
} }
private InputStream createInputStream() {
var string = input.stream().map(secret -> secret.getSecretValue()).collect(Collectors.joining("\n")) + "\r\n";
return new ByteArrayInputStream(string.getBytes(StandardCharsets.US_ASCII));
}
@Override @Override
public void start() throws IOException { public void start() throws IOException {
process = builder.start(); process = builder.start();
var t = new Thread(() -> { var t = new Thread(() -> {
try { try (var inputStream = createInputStream()){
input.transferTo(process.getOutputStream()); process.getOutputStream().flush();
inputStream.transferTo(process.getOutputStream());
process.getOutputStream().close(); process.getOutputStream().close();
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
@ -53,8 +65,12 @@ public class LocalStore implements ShellProcessStore {
@Override @Override
public int waitFor() throws Exception { public int waitFor() throws Exception {
if (timeout != null) {
return process.waitFor(timeout, TimeUnit.SECONDS) ? 0 : -1;
} else {
return process.waitFor(); return process.waitFor();
} }
}
@Override @Override
public InputStream getStdout() { public InputStream getStdout() {
@ -68,7 +84,11 @@ public class LocalStore implements ShellProcessStore {
@Override @Override
public Charset getCharset() { public Charset getCharset() {
return StandardCharsets.UTF_8; return StandardCharsets.US_ASCII;
}
public Integer getTimeout() {
return timeout;
} }
} }
@ -78,8 +98,8 @@ public class LocalStore implements ShellProcessStore {
} }
@Override @Override
public String toDisplay() { public String toSummaryString() {
return "local"; return "localhost";
} }
@Override @Override
@ -95,17 +115,17 @@ public class LocalStore implements ShellProcessStore {
} }
@Override @Override
public ProcessControl prepareCommand(InputStream input, List<String> cmd) { public ProcessControl prepareCommand(List<Secret> input, List<String> cmd, Integer timeout) {
return new LocalProcessControl(input, cmd); return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout));
} }
@Override @Override
public ProcessControl preparePrivilegedCommand(InputStream input, List<String> cmd) throws Exception { public ProcessControl preparePrivilegedCommand(List<Secret> input, List<String> cmd, Integer timeOut) throws Exception {
return new LocalProcessControl(input, cmd); return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut));
} }
@Override @Override
public ShellType determineType() { public ShellType determineType() throws Exception {
return ShellTypes.CMD; return ShellTypes.determine(this);
} }
} }

View file

@ -5,10 +5,11 @@ import java.io.OutputStream;
public interface MachineFileStore extends DataStore { public interface MachineFileStore extends DataStore {
static MachineFileStore local() { default boolean isLocal(){
return new LocalStore(); return false;
} }
InputStream openInput(String file) throws Exception; InputStream openInput(String file) throws Exception;
OutputStream openOutput(String file) throws Exception; OutputStream openOutput(String file) throws Exception;

View file

@ -33,7 +33,7 @@ public final class NamedStore implements DataStore {
} }
@Override @Override
public String toDisplay() { public String toSummaryString() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View file

@ -14,7 +14,6 @@ public abstract class ProcessControl {
start(); start();
var errT = discardErr(); var errT = discardErr();
var string = new String(getStdout().readAllBytes(), getCharset()); var string = new String(getStdout().readAllBytes(), getCharset());
errT.join();
waitFor(); waitFor();
return string; return string;
} }
@ -34,9 +33,6 @@ public abstract class ProcessControl {
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
outT.join();
t.join();
var ec = waitFor(); var ec = waitFor();
return ec != 0 ? Optional.of(read.get()) : Optional.empty(); return ec != 0 ? Optional.of(read.get()) : Optional.empty();
} }

View file

@ -0,0 +1,23 @@
package io.xpipe.core.store;
public class ProcessOutputException extends Exception{
public ProcessOutputException() {
super();
}
public ProcessOutputException(String message) {
super(message);
}
public ProcessOutputException(String message, Throwable cause) {
super(message, cause);
}
public ProcessOutputException(Throwable cause) {
super(cause);
}
protected ProcessOutputException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View file

@ -1,37 +0,0 @@
package io.xpipe.core.store;
import java.io.InputStream;
import java.io.OutputStream;
public interface ShellProcessStore extends StandardShellStore {
ShellType determineType() throws Exception;
@Override
default InputStream openInput(String file) throws Exception {
var type = determineType();
var cmd = type.createFileReadCommand(file);
var p = prepareCommand(InputStream.nullInputStream(), cmd);
p.start();
return p.getStdout();
}
@Override
default OutputStream openOutput(String file) throws Exception {
return null;
// var type = determineType();
// var cmd = type.createFileWriteCommand(file);
// var p = prepare(cmd).redirectErrorStream(true);
// var proc = p.start();
// return proc.getOutputStream();
}
@Override
default boolean exists(String file) throws Exception {
var type = determineType();
var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(InputStream.nullInputStream(), cmd);
p.start();
return p.waitFor() == 0;
}
}

View file

@ -1,35 +1,61 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.util.Secret;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
public interface ShellStore extends MachineFileStore { public abstract class ShellStore implements DataStore {
static StandardShellStore local() { public Integer getTimeout() {
return new LocalStore(); return null;
} }
default String executeAndRead(List<String> cmd) throws Exception { public List<Secret> getInput() {
var pc = prepareCommand(InputStream.nullInputStream(), cmd); return List.of();
}
public boolean isLocal() {
return false;
}
public String executeAndRead(List<String> cmd, Integer timeout) throws Exception {
var pc = prepareCommand(List.of(), cmd, getEffectiveTimeOut(timeout));
pc.start(); pc.start();
pc.discardErr(); pc.discardErr();
var string = new String(pc.getStdout().readAllBytes(), pc.getCharset()); var string = new String(pc.getStdout().readAllBytes(), pc.getCharset());
return string; return string;
} }
default Optional<String> executeAndCheckOut(InputStream in, List<String> cmd) throws Exception { public String executeAndCheckOut(List<Secret> in, List<String> cmd, Integer timeout) throws ProcessOutputException, Exception {
var pc = prepareCommand(in, cmd); var pc = prepareCommand(in, cmd, getEffectiveTimeOut(timeout));
pc.start(); pc.start();
var outT = pc.discardErr();
AtomicReference<String> read = new AtomicReference<>(); AtomicReference<String> readError = new AtomicReference<>();
var errorThread = new Thread(() -> {
try {
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
errorThread.setDaemon(true);
errorThread.start();
var read = new ByteArrayOutputStream();
var t = new Thread(() -> { var t = new Thread(() -> {
try { try {
read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset())); final byte[] buf = new byte[1];
int length;
while ((length = pc.getStdout().read(buf)) > 0) {
read.write(buf, 0, length);
}
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
@ -37,15 +63,21 @@ public interface ShellStore extends MachineFileStore {
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
outT.join();
t.join();
var ec = pc.waitFor(); var ec = pc.waitFor();
return ec == 0 ? Optional.of(read.get()) : Optional.empty(); var readOut = read.toString(pc.getCharset());
if (ec == -1) {
throw new ProcessOutputException("Command timed out");
} }
default Optional<String> executeAndCheckErr(InputStream in, List<String> cmd) throws Exception { if (ec == 0) {
var pc = prepareCommand(in, cmd); return readOut;
} else {
throw new ProcessOutputException("Command returned with " + ec + ": " + readError.get());
}
}
public Optional<String> executeAndCheckErr(List<Secret> in, List<String> cmd) throws Exception {
var pc = prepareCommand(in, cmd, getTimeout());
pc.start(); pc.start();
var outT = pc.discardOut(); var outT = pc.discardOut();
@ -67,17 +99,27 @@ public interface ShellStore extends MachineFileStore {
return ec != 0 ? Optional.of(read.get()) : Optional.empty(); return ec != 0 ? Optional.of(read.get()) : Optional.empty();
} }
default ProcessControl prepareCommand(List<String> cmd) throws Exception { public Integer getEffectiveTimeOut(Integer timeout) {
return prepareCommand(InputStream.nullInputStream(), cmd); if (this.getTimeout() == null) {
return timeout;
}
if (timeout == null) {
return getTimeout();
}
return Math.min(getTimeout(), timeout);
} }
ProcessControl prepareCommand(InputStream input, List<String> cmd) throws Exception; public ProcessControl prepareCommand(List<String> cmd, Integer timeout) throws Exception {
return prepareCommand(List.of(), cmd, timeout);
default ProcessControl preparePrivilegedCommand(List<String> cmd) throws Exception {
return preparePrivilegedCommand(InputStream.nullInputStream(), cmd);
} }
default ProcessControl preparePrivilegedCommand(InputStream input, List<String> cmd) throws Exception { public abstract ProcessControl prepareCommand(List<Secret> input, List<String> cmd, Integer timeout) throws Exception;
public ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout) throws Exception {
return preparePrivilegedCommand(List.of(), cmd, timeout);
}
public ProcessControl preparePrivilegedCommand(List<Secret> input, List<String> cmd, Integer timeout) throws Exception {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View file

@ -1,7 +1,9 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.xpipe.core.util.Secret;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -10,28 +12,28 @@ import java.util.List;
public class ShellTypes { public class ShellTypes {
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception { public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0")); var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null).strip();
if (o.isPresent() && !o.get().equals("$0")) { if (!o.equals("$0")) {
return SH; return SH;
} else { } else {
o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell")); o = store.executeAndCheckOut(List.of(), List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"), null).trim();
if (o.isPresent() && o.get().equals("PowerShell")) { if (o.equals("PowerShell")) {
return POWERSHELL; return POWERSHELL;
} else { } else
return CMD; return CMD;
} }
} }
}
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception { public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
var o = store.executeAndCheckOut(InputStream.nullInputStream(), List.of("echo", "$0")); var o = store.executeAndCheckOut(List.of(), List.of("echo", "$0"), null);
if (o.isPresent() && !o.get().trim().equals("$0")) { if (!o.trim().equals("$0")) {
return getLinuxShells(); return getLinuxShells();
} else { } else {
return getWindowsShells(); return getWindowsShells();
} }
} }
@JsonProperty("powershell")
public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() { public static final StandardShellStore.ShellType POWERSHELL = new StandardShellStore.ShellType() {
@Override @Override
@ -70,8 +72,14 @@ public class ShellTypes {
public String getDisplayName() { public String getDisplayName() {
return "PowerShell"; return "PowerShell";
} }
@Override
public List<String> getOperatingSystemNameCommand() {
return List.of("systeminfo", "|", "findstr", "/B", "/C:\"OS Name\"");
}
}; };
@JsonProperty("cmd")
public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() { public static final StandardShellStore.ShellType CMD = new StandardShellStore.ShellType() {
@Override @Override
@ -83,9 +91,9 @@ public class ShellTypes {
} }
@Override @Override
public ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception { public ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0"); var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
return st.prepareCommand(InputStream.nullInputStream(), l); return st.prepareCommand(List.of(), l, timeout);
} }
@Override @Override
@ -117,8 +125,14 @@ public class ShellTypes {
public String getDisplayName() { public String getDisplayName() {
return "cmd.exe"; return "cmd.exe";
} }
@Override
public List<String> getOperatingSystemNameCommand() {
return List.of("Get-ComputerInfo");
}
}; };
@JsonProperty("sh")
public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() { public static final StandardShellStore.ShellType SH = new StandardShellStore.ShellType() {
@Override @Override
@ -127,12 +141,12 @@ public class ShellTypes {
} }
@Override @Override
public ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception { public ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
var l = new ArrayList<>(cmd); var l = new ArrayList<>(cmd);
l.add(0, "sudo"); l.add(0, "sudo");
l.add(1, "-S"); l.add(1, "-S");
var pws = new ByteArrayInputStream(pw.getBytes(getCharset())); var pws = new ByteArrayInputStream(pw.getBytes(getCharset()));
return st.prepareCommand(pws, l); return st.prepareCommand(List.of(Secret.createForSecretValue(pw)), l, timeout);
} }
@Override @Override
@ -164,6 +178,11 @@ public class ShellTypes {
public String getDisplayName() { public String getDisplayName() {
return "/bin/sh"; return "/bin/sh";
} }
@Override
public List<String> getOperatingSystemNameCommand() {
return List.of("uname", "-o");
}
}; };
public static StandardShellStore.ShellType getDefault() { public static StandardShellStore.ShellType getDefault() {

View file

@ -1,17 +1,21 @@
package io.xpipe.core.store; package io.xpipe.core.store;
import io.xpipe.core.util.Secret;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; import java.util.List;
public interface StandardShellStore extends ShellStore { public abstract class StandardShellStore extends ShellStore implements MachineFileStore {
static interface ShellType {
public static interface ShellType {
List<String> switchTo(List<String> cmd); List<String> switchTo(List<String> cmd);
default ProcessControl prepareElevatedCommand(ShellStore st, InputStream in, List<String> cmd, String pw) throws Exception { default ProcessControl prepareElevatedCommand(ShellStore st, List<Secret> in, List<String> cmd, Integer timeout, String pw) throws Exception {
return st.prepareCommand(in, cmd); return st.prepareCommand(in, cmd, timeout);
} }
List<String> createFileReadCommand(String file); List<String> createFileReadCommand(String file);
@ -25,7 +29,42 @@ public interface StandardShellStore extends ShellStore {
String getName(); String getName();
String getDisplayName(); String getDisplayName();
List<String> getOperatingSystemNameCommand();
} }
ShellType determineType() throws Exception; public abstract ShellType determineType() throws Exception;
public final String querySystemName() throws Exception {
var result = executeAndCheckOut(List.of(), determineType().getOperatingSystemNameCommand(), getTimeout());
return result.strip();
}
@Override
public InputStream openInput(String file) throws Exception {
var type = determineType();
var cmd = type.createFileReadCommand(file);
var p = prepareCommand(List.of(), cmd, null);
p.start();
return p.getStdout();
}
@Override
public OutputStream openOutput(String file) throws Exception {
return null;
// var type = determineType();
// var cmd = type.createFileWriteCommand(file);
// var p = prepare(cmd).redirectErrorStream(true);
// var proc = p.start();
// return proc.getOutputStream();
}
@Override
public boolean exists(String file) throws Exception {
var type = determineType();
var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(List.of(), cmd, null);
p.start();
return p.waitFor() == 0;
}
} }

View file

@ -20,8 +20,8 @@ module io.xpipe.core {
opens io.xpipe.core.data.typed; opens io.xpipe.core.data.typed;
opens io.xpipe.core.dialog; opens io.xpipe.core.dialog;
requires com.fasterxml.jackson.core; requires static com.fasterxml.jackson.core;
requires com.fasterxml.jackson.databind; requires static com.fasterxml.jackson.databind;
requires java.net.http; requires java.net.http;
requires static lombok; requires static lombok;

1
deps

@ -1 +0,0 @@
Subproject commit 49a1ad06bc6872f72c1d20ea864d24f3df59b7c5

View file

@ -25,17 +25,13 @@ version = file('../misc/version').text
group = 'io.xpipe' group = 'io.xpipe'
archivesBaseName = 'extension' archivesBaseName = 'extension'
repositories {
mavenCentral()
}
dependencies { dependencies {
compileOnly 'net.synedra:validatorfx:0.3.1' compileOnly 'net.synedra:validatorfx:0.3.1'
compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2' compileOnly 'org.junit.jupiter:junit-jupiter-api:5.8.2'
compileOnly 'com.jfoenix:jfoenix:9.0.10' compileOnly 'com.jfoenix:jfoenix:9.0.10'
implementation project(':core') implementation project(':core')
implementation project(':fxcomps') // implementation project(':fxcomps')
//implementation 'io.xpipe:fxcomps:0.2' implementation 'io.xpipe:fxcomps:0.2.1'
implementation 'org.controlsfx:controlsfx:11.1.1' implementation 'org.controlsfx:controlsfx:11.1.1'
} }

View file

@ -15,7 +15,7 @@ import java.util.Map;
public interface DataSourceProvider<T extends DataSource<?>> { public interface DataSourceProvider<T extends DataSource<?>> {
static enum GeneralType { static enum Category {
FILE, FILE,
DATABASE; DATABASE;
} }
@ -25,13 +25,13 @@ public interface DataSourceProvider<T extends DataSource<?>> {
getSourceClass(); getSourceClass();
} }
default GeneralType getGeneralType() { default Category getGeneralType() {
if (getFileProvider() != null) { if (getFileProvider() != null) {
return GeneralType.FILE; return Category.FILE;
} }
if (getDatabaseProvider() != null) { if (getDatabaseProvider() != null) {
return GeneralType.DATABASE; return Category.DATABASE;
} }
throw new ExtensionException("Provider has no general type"); throw new ExtensionException("Provider has no general type");
@ -56,7 +56,7 @@ public interface DataSourceProvider<T extends DataSource<?>> {
return getId() + "." + key; return getId() + "." + key;
} }
default Region createConfigGui(Property<T> source) { default Region createConfigGui(Property<T> source, Property<T> appliedSource) {
return null; return null;
} }

View file

@ -1,10 +1,7 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore; import io.xpipe.core.store.*;
import io.xpipe.core.store.MachineFileStore;
import io.xpipe.core.store.ShellStore;
import io.xpipe.core.store.StreamDataStore;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import java.net.URI; import java.net.URI;
@ -35,9 +32,14 @@ public interface DataStoreProvider {
throw new ExtensionException("Gui Dialog is not implemented by provider " + getId()); throw new ExtensionException("Gui Dialog is not implemented by provider " + getId());
} }
default void init() throws Exception { default boolean init() throws Exception {
return true;
} }
String queryInformationString(DataStore store, int length) throws Exception;
public String toSummaryString(DataStore store, int length);
default boolean isHidden() { default boolean isHidden() {
return false; return false;
} }
@ -81,7 +83,7 @@ public interface DataStoreProvider {
} }
default String display(DataStore store) { default String display(DataStore store) {
return store.toDisplay(); return store.toSummaryString();
} }
List<String> getPossibleNames(); List<String> getPossibleNames();

View file

@ -1,6 +1,7 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.dialog.Dialog; import io.xpipe.core.dialog.Dialog;
import io.xpipe.core.store.DataStore;
import io.xpipe.extension.event.ErrorEvent; import io.xpipe.extension.event.ErrorEvent;
import java.net.URI; import java.net.URI;
@ -53,6 +54,11 @@ public class DataStoreProviders {
return ALL.stream().map(d -> d.dialogForString(s)).filter(Objects::nonNull).findAny(); return ALL.stream().map(d -> d.dialogForString(s)).filter(Objects::nonNull).findAny();
} }
public static <T extends DataStoreProvider> T byStore(DataStore store) {
return byStoreClass(store.getClass());
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) { public static <T extends DataStoreProvider> T byStoreClass(Class<?> c) {
if (ALL == null) { if (ALL == null) {

View file

@ -1,28 +1,57 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.source.DataSource;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import lombok.AllArgsConstructor;
import java.util.function.Supplier; import lombok.Value;
public interface SupportedApplicationProvider { public interface SupportedApplicationProvider {
enum Category { enum Category {
PROGRAMMING_LANGUAGE, PROGRAMMING_LANGUAGE,
APPLICATION APPLICATION,
OTHER
} }
Region createRetrieveInstructions(ObservableValue<String> id); enum AccessType {
ACTIVE,
PASSIVE
}
@Value
@AllArgsConstructor
public static class InstructionsDisplay {
Region region;
Runnable onFinish;
public InstructionsDisplay(Region region) {
this.region = region;
onFinish = null;
}
}
default InstructionsDisplay createRetrievalInstructions(DataSource<?> source, ObservableValue<String> id) {
return null;
}
default InstructionsDisplay createUpdateInstructions(DataSource<?> source, ObservableValue<String> id) {
return null;
}
String getId(); String getId();
Supplier<String> getName(); ObservableValue<String> getName();
Category getCategory(); Category getCategory();
AccessType getAccessType();
String getSetupGuideURL(); String getSetupGuideURL();
default String getGraphicIcon() { default String getGraphicIcon() {
return null; return null;
} }
} }

View file

@ -10,7 +10,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
@Override @Override
default Dialog configDialog(T source, boolean all) { default Dialog configDialog(T source, boolean all) {
return Dialog.empty(); return Dialog.empty().evaluateTo(() -> source);
} }
@Override @Override
@ -19,7 +19,7 @@ public interface UniformDataSourceProvider<T extends DataSource<?>> extends Data
try { try {
return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input); return (T) getSourceClass().getDeclaredConstructors()[0].newInstance(input);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new AssertionError(e); throw new ExtensionException(e);
} }
} }
} }

View file

@ -1,8 +1,11 @@
package io.xpipe.extension; package io.xpipe.extension;
import io.xpipe.core.store.ShellStore;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import net.synedra.validatorfx.Check; import net.synedra.validatorfx.Check;
import java.util.function.Predicate;
public class Validators { public class Validators {
public static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) { public static Check nonNull(Validator v, ObservableValue<String> name, ObservableValue<?> s) {
@ -12,4 +15,16 @@ public class Validators {
} }
}); });
} }
public static void nonNull(Object object, String name) {
if (object == null) {
throw new IllegalArgumentException(I18n.get("extension.null", name));
}
}
public static void hostFeature(ShellStore host, Predicate<ShellStore> predicate, String name) {
if (!predicate.test(host)) {
throw new IllegalArgumentException(I18n.get("extension.hostFeatureUnsupported", name));
}
}
} }

View file

@ -4,42 +4,33 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap; import java.util.HashMap;
import java.util.Map;
public class CharChoiceComp extends Comp<CompStructure<HBox>> { public class CharChoiceComp extends Comp<CompStructure<HBox>> {
private final Property<Character> value; private final Property<Character> value;
private final Property<Character> charChoiceValue; private final Map<Character, ObservableValue<String>> range;
private final BidiMap<Character, ObservableValue<String>> range;
private final ObservableValue<String> customName; private final ObservableValue<String> customName;
public CharChoiceComp(Property<Character> value, BidiMap<Character, ObservableValue<String>> range, ObservableValue<String> customName) { public CharChoiceComp(Property<Character> value, Map<Character, ObservableValue<String>> range, ObservableValue<String> customName) {
this.value = value; this.value = value;
this.range = range; this.range = range;
this.customName = customName; this.customName = customName;
this.charChoiceValue = new SimpleObjectProperty<>(range.containsKey(value.getValue()) ? value.getValue() : null);
value.addListener((c, o, n) -> {
if (!range.containsKey(n)) {
charChoiceValue.setValue(null);
} else {
charChoiceValue.setValue(n);
}
});
} }
@Override @Override
public CompStructure<HBox> createBase() { public CompStructure<HBox> createBase() {
var charChoice = new CharComp(value); var charChoice = new CharComp(value);
var rangeCopy = new DualLinkedHashBidiMap<>(range); var rangeCopy = new HashMap<>(range);
if (customName != null) { if (customName != null) {
rangeCopy.put(null, customName); rangeCopy.put(null, customName);
} }
var choice = new ChoiceComp<Character>(charChoiceValue, rangeCopy); var choice = new ChoiceComp<Character>(value, rangeCopy);
var charChoiceR = charChoice.createRegion(); var charChoiceR = charChoice.createRegion();
var choiceR = choice.createRegion(); var choiceR = choice.createRegion();
var box = new HBox(charChoiceR, choiceR); var box = new HBox(charChoiceR, choiceR);

View file

@ -7,10 +7,11 @@ import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> { public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Charset>>> {
@ -23,9 +24,10 @@ public class CharsetChoiceComp extends ReplacementComp<CompStructure<ComboBox<Ch
@Override @Override
protected Comp<CompStructure<ComboBox<Charset>>> createComp() { protected Comp<CompStructure<ComboBox<Charset>>> createComp() {
var map = new LinkedHashMap<Charset, ObservableValue<String>>(); var map = new LinkedHashMap<Charset, ObservableValue<String>>();
for (var e : Charset.availableCharsets().entrySet()) { for (var e : List.of(StandardCharsets.UTF_8, StandardCharsets.UTF_16,
map.put(e.getValue(), new SimpleStringProperty(e.getKey())); StandardCharsets.UTF_16BE, StandardCharsets.ISO_8859_1, Charset.forName("Windows-1251"), Charset.forName("Windows-1252"), StandardCharsets.US_ASCII)) {
map.put(e, new SimpleStringProperty(e.displayName()));
} }
return new ChoiceComp<>(charset, new DualLinkedHashBidiMap<>(map)); return new ChoiceComp<>(charset, map);
} }
} }

View file

@ -5,42 +5,65 @@ import io.xpipe.fxcomps.Comp;
import io.xpipe.fxcomps.CompStructure; import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.PlatformThread; import io.xpipe.fxcomps.util.PlatformThread;
import io.xpipe.fxcomps.util.SimpleChangeListener;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import org.apache.commons.collections4.BidiMap; import lombok.Value;
import java.util.Map;
@Value
public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> { public class ChoiceComp<T> extends Comp<CompStructure<ComboBox<T>>> {
private final Property<T> value; Property<T> value;
private final BidiMap<T, ObservableValue<String>> range; ObservableValue<Map<T, ObservableValue<String>>> range;
public ChoiceComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) { public ChoiceComp(Property<T> value, Map<T, ObservableValue<String>> range) {
this.value = value;
this.range = new SimpleObjectProperty<>(range);
}
public ChoiceComp(Property<T> value, ObservableValue<Map<T, ObservableValue<String>>> range) {
this.value = value; this.value = value;
this.range = range; this.range = range;
} }
@Override @Override
public CompStructure<ComboBox<T>> createBase() { public CompStructure<ComboBox<T>> createBase() {
var list = FXCollections.observableArrayList(range.keySet()); var cb = new ComboBox<T>();
var cb = new ComboBox<>(list); cb.setConverter(new StringConverter<T>() {
cb.setConverter(new StringConverter<>() {
@Override @Override
public String toString(T object) { public String toString(T object) {
if (object == null) { if (object == null) {
return I18n.get("extension.none"); return I18n.get("extension.none");
} }
return range.get(object).getValue(); var found = range.getValue().get(object);
if (found == null) {
return "";
} }
return found.getValue();
}
@Override @Override
public T fromString(String string) { public T fromString(String string) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
}); });
SimpleChangeListener.apply(PlatformThread.sync(range), c -> {
var list = FXCollections.observableArrayList(c.keySet());
if (!list.contains(null)) {
list.add(null);
}
cb.setItems(list);
});
PlatformThread.connect(value, cb.valueProperty()); PlatformThread.connect(value, cb.valueProperty());
cb.getStyleClass().add("choice-comp"); cb.getStyleClass().add("choice-comp");
return new SimpleCompStructure<>(cb); return new SimpleCompStructure<>(cb);

View file

@ -11,7 +11,6 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.Region; import javafx.scene.layout.Region;
import net.synedra.validatorfx.Check; import net.synedra.validatorfx.Check;
import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
@ -24,6 +23,7 @@ public class DynamicOptionsBuilder<T> {
private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>(); private final List<DynamicOptionsComp.Entry> entries = new ArrayList<>();
private final List<Property<?>> props = new ArrayList<>(); private final List<Property<?>> props = new ArrayList<>();
private final List<Property<?>> lazyProperties = new ArrayList<>();
private final ObservableValue<String> title; private final ObservableValue<String> title;
private final boolean wrap; private final boolean wrap;
@ -43,7 +43,15 @@ public class DynamicOptionsBuilder<T> {
this.title = title; this.title = title;
} }
public DynamicOptionsBuilder<T> makeLazy() {
var p = props.get(props.size() - 1);
props.remove(p);
lazyProperties.add(p);
return this;
}
public DynamicOptionsBuilder<T> decorate(Check c) { public DynamicOptionsBuilder<T> decorate(Check c) {
entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get())); entries.get(entries.size() - 1).comp().apply(s -> c.decorates(s.get()));
return this; return this;
} }
@ -59,28 +67,42 @@ public class DynamicOptionsBuilder<T> {
for (var e : NewLine.values()) { for (var e : NewLine.values()) {
map.put(e, I18n.observable("extension." + e.getId())); map.put(e, I18n.observable("extension." + e.getId()));
} }
var comp = new ChoiceComp<>(prop, new DualLinkedHashBidiMap<>(map)); var comp = new ChoiceComp<>(prop,map);
entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp)); entries.add(new DynamicOptionsComp.Entry(I18n.observable("extension.newLine"), comp));
props.add(prop); props.add(prop);
return this; return this;
} }
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names) { public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names) {
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), null); var comp = new CharChoiceComp(prop, names, null);
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop); props.add(prop);
return this; return this;
} }
public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names, ObservableValue<String> customName) { public DynamicOptionsBuilder<T> addCharacter(Property<Character> prop, ObservableValue<String> name, Map<Character, ObservableValue<String>> names, ObservableValue<String> customName) {
var comp = new CharChoiceComp(prop, new DualLinkedHashBidiMap<>(names), customName); var comp = new CharChoiceComp(prop, names, customName);
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop); props.add(prop);
return this; return this;
} }
public <V> DynamicOptionsBuilder<T> addToggle(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) { public <V> DynamicOptionsBuilder<T> addToggle(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
var comp = new ToggleGroupComp<>(prop, new DualLinkedHashBidiMap<>(names)); var comp = new ToggleGroupComp<>(prop, names);
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);
return this;
}
public <V> DynamicOptionsBuilder<T> addChoice(Property<V> prop, ObservableValue<String> name, Map<V, ObservableValue<String>> names) {
var comp = new ChoiceComp<>(prop, names);
entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop);
return this;
}
public <V> DynamicOptionsBuilder<T> addChoice(Property<V> prop, ObservableValue<String> name, ObservableValue<Map<V, ObservableValue<String>>> names) {
var comp = new ChoiceComp<>(prop, names);
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
props.add(prop); props.add(prop);
return this; return this;
@ -107,6 +129,14 @@ public class DynamicOptionsBuilder<T> {
return this; return this;
} }
public DynamicOptionsBuilder<T> addLazyString(String nameKey, Property<String> prop, Property<String> lazy) {
var comp = new TextFieldComp(prop, lazy);
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
props.add(prop);
lazyProperties.add(lazy);
return this;
}
public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) { public DynamicOptionsBuilder<T> addString(ObservableValue<String> name, Property<String> prop) {
var comp = new TextFieldComp(prop); var comp = new TextFieldComp(prop);
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
@ -120,6 +150,10 @@ public class DynamicOptionsBuilder<T> {
return this; return this;
} }
public DynamicOptionsBuilder<T> addSecret(String nameKey, Property<Secret> prop) {
return addSecret(I18n.observable(nameKey), prop);
}
public DynamicOptionsBuilder<T> addSecret(ObservableValue<String> name, Property<Secret> prop) { public DynamicOptionsBuilder<T> addSecret(ObservableValue<String> name, Property<Secret> prop) {
var comp = new SecretFieldComp(prop); var comp = new SecretFieldComp(prop);
entries.add(new DynamicOptionsComp.Entry(name, comp)); entries.add(new DynamicOptionsComp.Entry(name, comp));
@ -134,20 +168,61 @@ public class DynamicOptionsBuilder<T> {
return this; return this;
} }
public <V extends T> Comp<?> buildComp(Supplier<V> creator, Property<T> toSet) { public DynamicOptionsBuilder<T> addInteger(String nameKey, Property<Integer> prop) {
var comp = new IntFieldComp(prop);
entries.add(new DynamicOptionsComp.Entry(I18n.observable(nameKey), comp));
props.add(prop);
return this;
}
@SafeVarargs
public final <V extends T> DynamicOptionsBuilder<T> bind(Supplier<V> creator, Property<T>... toSet) {
props.forEach(prop -> { props.forEach(prop -> {
prop.addListener((c, o, n) -> { prop.addListener((c, o, n) -> {
toSet.setValue(creator.get()); for (Property<T> p : toSet) {
p.setValue(creator.get());
}
}); });
}); });
toSet.setValue(creator.get()); for (Property<T> p : toSet) {
p.setValue(creator.get());
}
return this;
}
public <V extends T> DynamicOptionsBuilder<T> bindLazy(Supplier<V> creator, Property<T> toLazySet) {
lazyProperties.forEach(prop -> {
prop.addListener((c,o,n) -> {
toLazySet.setValue(creator.get());
});
});
toLazySet.setValue(creator.get());
return this;
}
public <V extends T> Comp<?> buildComp() {
if (title != null) { if (title != null) {
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title"))); entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
} }
return new DynamicOptionsComp(entries, wrap); return new DynamicOptionsComp(entries, wrap);
} }
public <V extends T> Region build(Supplier<V> creator, Property<T> toSet) { public <V extends T> Comp<?> buildBindingComp(Supplier<Property<? extends V>> creator, Property<T> toSet) {
return buildComp(creator, toSet).createRegion(); props.forEach(prop -> {
prop.addListener((c,o,n) -> {
toSet.unbind();
toSet.bind(creator.get());
});
});
toSet.bind(creator.get());
if (title != null) {
entries.add(0, new DynamicOptionsComp.Entry(null, Comp.of(() -> new Label(title.getValue())).styleClass("title")));
}
return new DynamicOptionsComp(entries, wrap);
}
public <V extends T> Region build() {
return buildComp().createRegion();
} }
} }

View file

@ -22,7 +22,7 @@ public class SecretFieldComp extends Comp<CompStructure<TextField>> {
var text = new PasswordField(); var text = new PasswordField();
text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null); text.setText(value.getValue() != null ? value.getValue().getSecretValue() : null);
text.textProperty().addListener((c, o, n) -> { text.textProperty().addListener((c, o, n) -> {
value.setValue(n.length() > 0 ? Secret.createForSecretValue(n) : null); value.setValue(n != null && n.length() > 0 ? Secret.createForSecretValue(n) : null);
}); });
value.addListener((c, o, n) -> { value.addListener((c, o, n) -> {
PlatformThread.runLaterIfNeeded(() -> { PlatformThread.runLaterIfNeeded(() -> {

View file

@ -25,7 +25,10 @@ public class TabPaneComp extends Comp<CompStructure<JFXTabPane>> {
for (var e : entries) { for (var e : entries) {
Tab tab = new Tab(); Tab tab = new Tab();
var ll = new Label(null, new FontIcon(e.graphic())); var ll = new Label(null);
if (e.graphic != null) {
ll.setGraphic(new FontIcon(e.graphic()));
}
ll.textProperty().bind(e.name()); ll.textProperty().bind(e.name());
ll.getStyleClass().add("name"); ll.getStyleClass().add("name");
ll.setAlignment(Pos.CENTER); ll.setAlignment(Pos.CENTER);

View file

@ -5,14 +5,25 @@ import io.xpipe.fxcomps.CompStructure;
import io.xpipe.fxcomps.SimpleCompStructure; import io.xpipe.fxcomps.SimpleCompStructure;
import io.xpipe.fxcomps.util.PlatformThread; import io.xpipe.fxcomps.util.PlatformThread;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.event.EventHandler;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class TextFieldComp extends Comp<CompStructure<TextField>> { public class TextFieldComp extends Comp<CompStructure<TextField>> {
private final Property<String> value; private final Property<String> value;
private final Property<String> lazyValue;
public TextFieldComp(Property<String> value) { public TextFieldComp(Property<String> value) {
this.value = value; this.value = value;
this.lazyValue = value;
}
public TextFieldComp(Property<String> value, Property<String> lazyValue) {
this.value = value;
this.lazyValue = lazyValue;
} }
@Override @Override
@ -26,6 +37,15 @@ public class TextFieldComp extends Comp<CompStructure<TextField>> {
text.setText(n); text.setText(n);
}); });
}); });
text.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent ke) {
if (ke.getCode().equals(KeyCode.ENTER)) {
lazyValue.setValue(value.getValue());
}
ke.consume();
}
});
return new SimpleCompStructure<>(text); return new SimpleCompStructure<>(text);
} }
} }

View file

@ -9,22 +9,19 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.ToggleButton; import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup; import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.apache.commons.collections4.BidiMap;
import java.util.Map;
public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> { public class ToggleGroupComp<T> extends Comp<CompStructure<HBox>> {
private final Property<T> value; private final Property<T> value;
private final BidiMap<T, ObservableValue<String>> range; private final Map<T, ObservableValue<String>> range;
public ToggleGroupComp(Property<T> value, BidiMap<T, ObservableValue<String>> range) { public ToggleGroupComp(Property<T> value, Map<T, ObservableValue<String>> range) {
this.value = value; this.value = value;
this.range = range; this.range = range;
} }
public BidiMap<T, ObservableValue<String>> getRange() {
return range;
}
@Override @Override
public CompStructure<HBox> createBase() { public CompStructure<HBox> createBase() {
var box = new HBox(); var box = new HBox();

View file

@ -33,6 +33,11 @@ public class ErrorEvent {
.description(msg); .description(msg);
} }
public static ErrorEventBuilder fromMessage(String msg) {
return builder()
.description(msg);
}
public void handle() { public void handle() {
EventHandler.get().handle(this); EventHandler.get().handle(this);
} }

View file

@ -2,9 +2,45 @@ package io.xpipe.extension.prefs;
import io.xpipe.extension.I18n; import io.xpipe.extension.I18n;
import io.xpipe.extension.Translatable; import io.xpipe.extension.Translatable;
import lombok.SneakyThrows;
import java.util.Arrays;
import java.util.List;
public interface PrefsChoiceValue extends Translatable { public interface PrefsChoiceValue extends Translatable {
@SuppressWarnings("unchecked")
@SneakyThrows
static <T> List<T> getAll(Class<T> type) {
if (Enum.class.isAssignableFrom(type)) {
return Arrays.asList(type.getEnumConstants());
}
try {
type.getDeclaredField("ALL");
} catch (NoSuchFieldException e) {
return null;
}
try {
return (List<T>) type.getDeclaredField("ALL").get(null);
} catch (IllegalAccessException | NoSuchFieldException e) {
return List.of(type.getEnumConstants());
}
}
static <T extends PrefsChoiceValue> List<T> getSupported(Class<T> type) {
try {
return (List<T>) type.getDeclaredField("SUPPORTED").get(null);
} catch (IllegalAccessException | NoSuchFieldException e) {
return getAll(type).stream().filter(t -> t.isSupported()).toList();
}
}
default boolean isSupported() {
return true;
}
@Override @Override
default String toTranslatedString() { default String toTranslatedString() {
return I18n.get(getId()); return I18n.get(getId());

View file

@ -13,7 +13,6 @@ module io.xpipe.extension {
requires javafx.graphics; requires javafx.graphics;
requires transitive javafx.controls; requires transitive javafx.controls;
requires io.xpipe.fxcomps; requires io.xpipe.fxcomps;
requires static org.apache.commons.collections4;
requires static lombok; requires static lombok;
requires static com.dlsc.preferencesfx; requires static com.dlsc.preferencesfx;
requires static com.dlsc.formsfx; requires static com.dlsc.formsfx;

View file

@ -5,3 +5,8 @@ lf=LF (Linux)
none=None none=None
nullPointer=Null Pointer: $MSG$ nullPointer=Null Pointer: $MSG$
mustNotBeEmpty=$NAME$ must not be empty mustNotBeEmpty=$NAME$ must not be empty
null=$VALUE$ must be not null
hostFeatureUnsupported=Host does not support the feature $FEATURE$
namedHostFeatureUnsupported=$HOST$ does not support this feature
namedHostNotActive=$HOST$ is not active
noInformationAvailable=No information available

0
gradlew vendored Executable file → Normal file
View file

View file

@ -5,8 +5,8 @@ def canonicalVersion = file('misc/canonical_version').text
jreleaser { jreleaser {
environment { environment {
properties.put('rawChangelog', file("misc/changelogs/${canonicalVersion}.txt").exists() ? properties.put('rawChangelog', file("misc/changelogs/${version}.txt").exists() ?
file("misc/changelogs/${canonicalVersion}.txt").text.replace('\r\n', '\n') : "") file("misc/changelogs/${version}.txt").text.replace('\r\n', '\n') : "")
} }
project { project {

View file

@ -1 +0,0 @@
0.1

View file

@ -2,10 +2,7 @@
🚀 {{projectName}} {{projectVersion}} has been released: {{releaseNotesUrl}} 🚀 {{projectName}} {{projectVersion}} has been released: {{releaseNotesUrl}}
If you have already installed {{projectName}} and have updates enabled, The documentation and maven repositories should be automatically updated within the next couple of hours.
this update will be automatically installed when launching it the next time.
You can get the standalone version here: {{releaseNotesUrl}}
Changes in {{projectVersion}}: Changes in {{projectVersion}}:
{{{rawChangelog}}} {{{rawChangelog}}}

View file

@ -5,8 +5,7 @@
Note that as this is not a final release, there might still be some small issues with it. Note that as this is not a final release, there might still be some small issues with it.
Please report them if you stumble upon one. Please report them if you stumble upon one.
Also note that pre-releases are not downloaded by the automatic updater The documentation and maven repositories should be automatically updated within the next couple of hours.
until they are ready for a full release or you have opted in to receiving pre-releases.
Changes in {{projectVersion}}: Changes in {{projectVersion}}:
{{{rawChangelog}}} {{{rawChangelog}}}

View file

@ -1 +1 @@
0.1-SNAPSHOT 0.0.1-SNAPSHOT

View file

@ -4,3 +4,12 @@ include 'api'
include 'core' include 'core'
include 'beacon' include 'beacon'
include 'extension' include 'extension'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0"
}
}