mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-25 00:50:31 +00:00
Merge branch 1.7 into master
This commit is contained in:
parent
2429a70c42
commit
d96a38d7b2
326 changed files with 2633 additions and 10111 deletions
|
@ -1,12 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface DataRaw extends DataSource {
|
||||
|
||||
InputStream open();
|
||||
|
||||
byte[] readAll();
|
||||
|
||||
byte[] read(int maxBytes);
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.api.impl.DataSourceImpl;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Represents a reference to a data source that is managed by XPipe.
|
||||
* <p>
|
||||
* The actual data is only queried when required and is not cached.
|
||||
* Therefore, the queried data is always up-to-date at the point of calling a method that queries the data.
|
||||
* <p>
|
||||
* As soon a data source reference is created, the data source is locked
|
||||
* within XPipe to prevent concurrent modification and the problems that can arise from it.
|
||||
* By default, the lock is held until the calling program terminates and prevents
|
||||
* other applications from modifying the data source in any way.
|
||||
* To unlock the data source earlier, you can make use the {@link #unlock()} method.
|
||||
*/
|
||||
public interface DataSource {
|
||||
|
||||
/**
|
||||
* NOT YET IMPLEMENTED!
|
||||
* <p>
|
||||
* Creates a new supplier data source that will be interpreted as the generated data source.
|
||||
* In case this program should be a data source generator, this method has to be called at
|
||||
* least once to register that it actually generates a data source.
|
||||
* <p>
|
||||
* All content that is written to this data source until the generator program terminates is
|
||||
* will be available later on when the data source is used as a supplier later on.
|
||||
* <p>
|
||||
* In case this method is called multiple times, the same data source is returned.
|
||||
*
|
||||
* @return the generator data source
|
||||
*/
|
||||
static DataSource drain() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOT YET IMPLEMENTED!
|
||||
* <p>
|
||||
* Creates a data source sink that will block with any read operations
|
||||
* until an external data producer routes the output into this sink.
|
||||
*/
|
||||
static DataSource sink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #get(DataSourceReference)}.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code id} is not a valid data source id
|
||||
*/
|
||||
static DataSource getById(String id) {
|
||||
return get(DataSourceReference.id(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #get(DataSourceReference)} using the latest reference.
|
||||
*/
|
||||
static DataSource getLatest() {
|
||||
return get(DataSourceReference.latest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #get(DataSourceReference)} using a name reference.
|
||||
*/
|
||||
static DataSource getByName(String name) {
|
||||
return get(DataSourceReference.name(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the data source for a given reference.
|
||||
*
|
||||
* @param ref the data source reference
|
||||
*/
|
||||
static DataSource get(DataSourceReference ref) {
|
||||
return DataSourceImpl.get(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the lock held by this program for this data source such
|
||||
* that other applications can modify the data source again.
|
||||
*/
|
||||
static void unlock() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, Path path) {
|
||||
return create(null, type, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)}.
|
||||
*/
|
||||
static DataSource create(DataStoreId id, String type, Path path) {
|
||||
try (var in = Files.newInputStream(path)) {
|
||||
return create(id, type, in);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, URL url) {
|
||||
return create(null, type, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)}.
|
||||
*/
|
||||
static DataSource create(DataStoreId id, String type, URL url) {
|
||||
try (var in = url.openStream()) {
|
||||
return create(id, type, in);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #create(DataStoreId, String, InputStream)} that creates an anonymous data source.
|
||||
*/
|
||||
static DataSource createAnonymous(String type, InputStream in) {
|
||||
return create(null, type, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data source from an input stream.
|
||||
*
|
||||
* @param id the data source id
|
||||
* @param type the data source type
|
||||
* @param in the input stream to read
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataStoreId id, String type, InputStream in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data source from an input stream.
|
||||
*
|
||||
* @param id the data source id
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataStoreId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
return DataSourceImpl.create(id, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new data source from an input stream.
|
||||
* 1
|
||||
*
|
||||
* @param id the data source id
|
||||
* @param type the data source type
|
||||
* @param in the data store to add
|
||||
* @return a {@link DataSource} instances that can be used to access the underlying data
|
||||
*/
|
||||
static DataSource create(DataStoreId id, String type, DataStore in) {
|
||||
return DataSourceImpl.create(id, type, in);
|
||||
}
|
||||
|
||||
void forwardTo(DataSource target);
|
||||
|
||||
void appendTo(DataSource target);
|
||||
|
||||
io.xpipe.core.source.DataSource<?> getInternalSource();
|
||||
|
||||
/**
|
||||
* Returns the id of this data source.
|
||||
*/
|
||||
DataStoreId getId();
|
||||
|
||||
/**
|
||||
* Returns the type of this data source.
|
||||
*/
|
||||
DataSourceType getType();
|
||||
|
||||
DataSourceConfig getConfig();
|
||||
|
||||
/**
|
||||
* Attempts to cast this object to a {@link DataTable}.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the data source is not a table
|
||||
*/
|
||||
default DataTable asTable() {
|
||||
throw new UnsupportedOperationException("Data source is not a table");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cast this object to a {@link DataStructure}.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the data source is not a structure
|
||||
*/
|
||||
default DataStructure asStructure() {
|
||||
throw new UnsupportedOperationException("Data source is not a structure");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cast this object to a {@link DataText}.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the data source is not a text
|
||||
*/
|
||||
default DataText asText() {
|
||||
throw new UnsupportedOperationException("Data source is not a text");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to cast this object to a {@link DataRaw}.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the data source is not raw
|
||||
*/
|
||||
default DataRaw asRaw() {
|
||||
throw new UnsupportedOperationException("Data source is not raw");
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents the current configuration of a data source.
|
||||
*/
|
||||
public final class DataSourceConfig {
|
||||
|
||||
/**
|
||||
* The data source provider id.
|
||||
*/
|
||||
private final String provider;
|
||||
|
||||
/**
|
||||
* The set configuration parameters.
|
||||
*/
|
||||
private final Map<String, String> configInstance;
|
||||
|
||||
public DataSourceConfig(String provider, Map<String, String> configInstance) {
|
||||
this.provider = provider;
|
||||
this.configInstance = configInstance;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return configInstance;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
|
||||
public interface DataStructure extends DataSource {
|
||||
DataStructureNode read();
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface DataTable extends Iterable<TupleNode>, DataSource {
|
||||
|
||||
Stream<TupleNode> stream();
|
||||
|
||||
ArrayNode readAll();
|
||||
|
||||
ArrayNode read(int maxRows);
|
||||
|
||||
default int countAndDiscard() {
|
||||
AtomicInteger count = new AtomicInteger();
|
||||
try (var stream = stream()) {
|
||||
stream.forEach(dataStructureNodes -> {
|
||||
count.getAndIncrement();
|
||||
});
|
||||
}
|
||||
return count.get();
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import io.xpipe.api.impl.DataTableAccumulatorImpl;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
|
||||
/**
|
||||
* An accumulator for table data.
|
||||
* <p>
|
||||
* This class can be used to construct new table data sources by
|
||||
* accumulating the rows using {@link #add(DataStructureNode)} or {@link #acceptor()} and then calling
|
||||
* {@link #finish(DataStoreId)} to complete the construction process and create a new data source.
|
||||
*/
|
||||
public interface DataTableAccumulator {
|
||||
|
||||
static DataTableAccumulator create(TupleType type) {
|
||||
return new DataTableAccumulatorImpl(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link #finish(DataStoreId)}.
|
||||
*/
|
||||
default DataTable finish(String id) {
|
||||
return finish(DataStoreId.fromString(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the construction process and returns the data source reference.
|
||||
*
|
||||
* @param id the data source id to assign
|
||||
*/
|
||||
DataTable finish(DataStoreId id);
|
||||
|
||||
/**
|
||||
* Adds a row to the table.
|
||||
*
|
||||
* @param row the row to add
|
||||
*/
|
||||
void add(DataStructureNode row);
|
||||
|
||||
/**
|
||||
* Creates a tuple acceptor that adds all accepted tuples to the table.
|
||||
*/
|
||||
DataStructureNodeAcceptor<DataStructureNode> acceptor();
|
||||
|
||||
/**
|
||||
* Returns the current amount of rows added to the table.
|
||||
*/
|
||||
int getCurrentRows();
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package io.xpipe.api;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface DataText extends DataSource {
|
||||
|
||||
List<String> readAllLines();
|
||||
|
||||
List<String> readLines(int maxLines);
|
||||
|
||||
Stream<String> lines();
|
||||
|
||||
String readAll();
|
||||
|
||||
String read(int maxCharacters);
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataRaw;
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class DataRawImpl extends DataSourceImpl implements DataRaw {
|
||||
|
||||
public DataRawImpl(
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream open() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readAll() {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(int maxBytes) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceType getType() {
|
||||
return DataSourceType.RAW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataRaw asRaw() {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataSource;
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.connector.XPipeApiConnection;
|
||||
import io.xpipe.beacon.exchange.*;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.StreamDataStore;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public abstract class DataSourceImpl implements DataSource {
|
||||
|
||||
private final DataStoreId sourceId;
|
||||
private final DataSourceConfig config;
|
||||
private final io.xpipe.core.source.DataSource<?> internalSource;
|
||||
|
||||
public DataSourceImpl(
|
||||
DataStoreId sourceId, DataSourceConfig config, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
this.sourceId = sourceId;
|
||||
this.config = config;
|
||||
this.internalSource = internalSource;
|
||||
}
|
||||
|
||||
public static DataSource get(DataSourceReference ds) {
|
||||
return XPipeApiConnection.execute(con -> {
|
||||
var req = QueryDataSourceExchange.Request.builder().ref(ds).build();
|
||||
QueryDataSourceExchange.Response res = con.performSimpleExchange(req);
|
||||
var config = new DataSourceConfig(res.getProvider(), res.getConfig());
|
||||
return switch (res.getType()) {
|
||||
case TABLE -> {
|
||||
yield new DataTableImpl(res.getId(), config, res.getInternalSource());
|
||||
}
|
||||
case STRUCTURE -> {
|
||||
yield new DataStructureImpl(res.getId(), config, res.getInternalSource());
|
||||
}
|
||||
case TEXT -> {
|
||||
yield new DataTextImpl(res.getId(), config, res.getInternalSource());
|
||||
}
|
||||
case RAW -> {
|
||||
yield new DataRawImpl(res.getId(), config, res.getInternalSource());
|
||||
}
|
||||
case COLLECTION -> throw new UnsupportedOperationException("Unimplemented case: " + res.getType());
|
||||
default -> throw new IllegalArgumentException("Unexpected value: " + res.getType());
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public static DataSource create(DataStoreId id, io.xpipe.core.source.DataSource<?> source) {
|
||||
var startReq =
|
||||
AddSourceExchange.Request.builder().source(source).target(id).build();
|
||||
var returnedId = XPipeApiConnection.execute(con -> {
|
||||
AddSourceExchange.Response r = con.performSimpleExchange(startReq);
|
||||
return r.getId();
|
||||
});
|
||||
|
||||
var ref = DataSourceReference.id(returnedId);
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataStoreId id, String type, DataStore store) {
|
||||
if (store instanceof StreamDataStore s && s.isContentExclusivelyAccessible()) {
|
||||
store = XPipeApiConnection.execute(con -> {
|
||||
var internal = con.createInternalStreamStore();
|
||||
var req = WriteStreamExchange.Request.builder()
|
||||
.name(internal.getUuid().toString())
|
||||
.build();
|
||||
con.performOutputExchange(req, out -> {
|
||||
try (InputStream inputStream = s.openInput()) {
|
||||
inputStream.transferTo(out);
|
||||
}
|
||||
});
|
||||
return internal;
|
||||
});
|
||||
}
|
||||
|
||||
var startReq = ReadExchange.Request.builder()
|
||||
.provider(type)
|
||||
.store(store)
|
||||
.target(id)
|
||||
.configureAll(false)
|
||||
.build();
|
||||
var startRes = XPipeApiConnection.execute(con -> {
|
||||
return con.<ReadExchange.Request, ReadExchange.Response>performSimpleExchange(startReq);
|
||||
});
|
||||
|
||||
var configInstance = startRes.getConfig();
|
||||
XPipeApiConnection.finishDialog(configInstance);
|
||||
|
||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
public static DataSource create(DataStoreId id, String type, InputStream in) {
|
||||
var store = XPipeApiConnection.execute(con -> {
|
||||
var internal = con.createInternalStreamStore();
|
||||
var req = WriteStreamExchange.Request.builder()
|
||||
.name(internal.getUuid().toString())
|
||||
.build();
|
||||
con.performOutputExchange(req, out -> {
|
||||
in.transferTo(out);
|
||||
});
|
||||
return internal;
|
||||
});
|
||||
|
||||
var startReq = ReadExchange.Request.builder()
|
||||
.provider(type)
|
||||
.store(store)
|
||||
.target(id)
|
||||
.configureAll(false)
|
||||
.build();
|
||||
var startRes = XPipeApiConnection.execute(con -> {
|
||||
return con.<ReadExchange.Request, ReadExchange.Response>performSimpleExchange(startReq);
|
||||
});
|
||||
|
||||
var configInstance = startRes.getConfig();
|
||||
XPipeApiConnection.finishDialog(configInstance);
|
||||
|
||||
var ref = id != null ? DataSourceReference.id(id) : DataSourceReference.latest();
|
||||
return get(ref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forwardTo(DataSource target) {
|
||||
XPipeApiConnection.execute(con -> {
|
||||
var req = ForwardExchange.Request.builder()
|
||||
.source(DataSourceReference.id(sourceId))
|
||||
.target(DataSourceReference.id(target.getId()))
|
||||
.build();
|
||||
ForwardExchange.Response res = con.performSimpleExchange(req);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendTo(DataSource target) {
|
||||
XPipeApiConnection.execute(con -> {
|
||||
var req = ForwardExchange.Request.builder()
|
||||
.source(DataSourceReference.id(sourceId))
|
||||
.target(DataSourceReference.id(target.getId()))
|
||||
.append(true)
|
||||
.build();
|
||||
ForwardExchange.Response res = con.performSimpleExchange(req);
|
||||
});
|
||||
}
|
||||
|
||||
public io.xpipe.core.source.DataSource<?> getInternalSource() {
|
||||
return internalSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStoreId getId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataStructure;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
public class DataStructureImpl extends DataSourceImpl implements DataStructure {
|
||||
|
||||
DataStructureImpl(
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceType getType() {
|
||||
return DataSourceType.STRUCTURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStructure asStructure() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataStructureNode read() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataSource;
|
||||
import io.xpipe.api.DataTable;
|
||||
import io.xpipe.api.DataTableAccumulator;
|
||||
import io.xpipe.api.connector.XPipeApiConnection;
|
||||
import io.xpipe.api.util.TypeDescriptor;
|
||||
import io.xpipe.beacon.BeaconException;
|
||||
import io.xpipe.beacon.exchange.ReadExchange;
|
||||
import io.xpipe.beacon.exchange.WriteStreamExchange;
|
||||
import io.xpipe.beacon.exchange.cli.StoreAddExchange;
|
||||
import io.xpipe.beacon.util.QuietDialogHandler;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.DataStructureNodeAcceptor;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
import io.xpipe.core.data.type.TupleType;
|
||||
import io.xpipe.core.data.typed.TypedDataStreamWriter;
|
||||
import io.xpipe.core.impl.InternalStreamStore;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceReference;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class DataTableAccumulatorImpl implements DataTableAccumulator {
|
||||
|
||||
private final XPipeApiConnection connection;
|
||||
private final TupleType type;
|
||||
private int rows;
|
||||
private final InternalStreamStore store;
|
||||
private TupleType writtenDescriptor;
|
||||
private final OutputStream bodyOutput;
|
||||
|
||||
public DataTableAccumulatorImpl(TupleType type) {
|
||||
this.type = type;
|
||||
connection = XPipeApiConnection.open();
|
||||
|
||||
store = new InternalStreamStore();
|
||||
var addReq = StoreAddExchange.Request.builder()
|
||||
.storeInput(store)
|
||||
.name(store.getUuid().toString())
|
||||
.build();
|
||||
StoreAddExchange.Response addRes = connection.performSimpleExchange(addReq);
|
||||
QuietDialogHandler.handle(addRes.getConfig(), connection);
|
||||
|
||||
connection.sendRequest(WriteStreamExchange.Request.builder()
|
||||
.name(store.getUuid().toString())
|
||||
.build());
|
||||
bodyOutput = connection.sendBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DataTable finish(DataStoreId id) {
|
||||
try {
|
||||
bodyOutput.close();
|
||||
} catch (IOException e) {
|
||||
throw new BeaconException(e);
|
||||
}
|
||||
|
||||
WriteStreamExchange.Response res = connection.receiveResponse();
|
||||
connection.close();
|
||||
|
||||
var req = ReadExchange.Request.builder()
|
||||
.target(id)
|
||||
.store(store)
|
||||
.provider("xpbt")
|
||||
.configureAll(false)
|
||||
.build();
|
||||
ReadExchange.Response response = XPipeApiConnection.execute(con -> {
|
||||
return con.performSimpleExchange(req);
|
||||
});
|
||||
|
||||
var configInstance = response.getConfig();
|
||||
XPipeApiConnection.finishDialog(configInstance);
|
||||
|
||||
return DataSource.get(DataSourceReference.id(id)).asTable();
|
||||
}
|
||||
|
||||
private void writeDescriptor() {
|
||||
if (writtenDescriptor != null) {
|
||||
return;
|
||||
}
|
||||
writtenDescriptor = TupleType.tableType(type.getNames());
|
||||
|
||||
connection.withOutputStream(out -> {
|
||||
out.write((TypeDescriptor.create(type.getNames())).getBytes(StandardCharsets.UTF_8));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void add(DataStructureNode row) {
|
||||
TupleNode toUse = type.matches(row)
|
||||
? row.asTuple()
|
||||
: type.convert(row).orElseThrow().asTuple();
|
||||
connection.withOutputStream(out -> {
|
||||
writeDescriptor();
|
||||
TypedDataStreamWriter.writeStructure(out, toUse, writtenDescriptor);
|
||||
rows++;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized DataStructureNodeAcceptor<DataStructureNode> acceptor() {
|
||||
return node -> {
|
||||
add(node);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getCurrentRows() {
|
||||
return rows;
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataTable;
|
||||
import io.xpipe.core.data.node.ArrayNode;
|
||||
import io.xpipe.core.data.node.DataStructureNode;
|
||||
import io.xpipe.core.data.node.TupleNode;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DataTableImpl extends DataSourceImpl implements DataTable {
|
||||
|
||||
DataTableImpl(DataStoreId id, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(id, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataTable asTable() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Stream<TupleNode> stream() {
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceType getType() {
|
||||
return DataSourceType.TABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayNode readAll() {
|
||||
return read(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayNode read(int maxRows) {
|
||||
List<DataStructureNode> nodes = new ArrayList<>();
|
||||
return ArrayNode.of(nodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TupleNode> iterator() {
|
||||
return new Iterator<>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TupleNode next() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package io.xpipe.api.impl;
|
||||
|
||||
import io.xpipe.api.DataSourceConfig;
|
||||
import io.xpipe.api.DataText;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DataTextImpl extends DataSourceImpl implements DataText {
|
||||
|
||||
DataTextImpl(
|
||||
DataStoreId sourceId, DataSourceConfig sourceConfig, io.xpipe.core.source.DataSource<?> internalSource) {
|
||||
super(sourceId, sourceConfig, internalSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSourceType getType() {
|
||||
return DataSourceType.TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataText asText() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> readAllLines() {
|
||||
return readLines(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> readLines(int maxLines) {
|
||||
try (Stream<String> lines = lines()) {
|
||||
return lines.limit(maxLines).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> lines() {
|
||||
return Stream.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readAll() {
|
||||
try (Stream<String> lines = lines()) {
|
||||
return lines.collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(int maxCharacters) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
lines().takeWhile(s -> {
|
||||
if (builder.length() > maxCharacters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.append(s);
|
||||
return true;
|
||||
});
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.api.test;
|
||||
|
||||
import io.xpipe.api.DataSource;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.store.DataStoreId;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ application {
|
|||
run {
|
||||
systemProperty 'io.xpipe.app.useVirtualThreads', 'false'
|
||||
systemProperty 'io.xpipe.app.mode', 'gui'
|
||||
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local_git2/"
|
||||
systemProperty 'io.xpipe.app.dataDir', "$projectDir/local_git3/"
|
||||
systemProperty 'io.xpipe.app.writeLogs', "true"
|
||||
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
||||
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||
|
|
|
@ -12,11 +12,10 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||
import io.xpipe.app.util.FixedHierarchyStore;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -53,7 +52,7 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
return (storeEntryWrapper.getEntry().getStore() instanceof ShellStore
|
||||
|| storeEntryWrapper.getEntry().getStore() instanceof FixedHierarchyStore)
|
||||
&& storeEntryWrapper.getEntry().getState().isUsable();
|
||||
&& storeEntryWrapper.getEntry().getValidity().isUsable();
|
||||
};
|
||||
var section = StoreSectionMiniComp.createList(
|
||||
StoreSection.createTopLevel(
|
||||
|
@ -68,7 +67,7 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
.pseudoClassStateChanged(
|
||||
SELECTED,
|
||||
newValue != null
|
||||
&& newValue.getStore()
|
||||
&& newValue.getEntry()
|
||||
.equals(s.getWrapper()
|
||||
.getEntry()
|
||||
.getStore()));
|
||||
|
@ -77,13 +76,10 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
ThreadHelper.runFailableAsync(() -> {
|
||||
var entry = s.getWrapper().getEntry();
|
||||
if (entry.getStore() instanceof ShellStore fileSystem) {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
s.getWrapper().refreshIfNeeded();
|
||||
});
|
||||
model.openFileSystemAsync(null, fileSystem, null, busy);
|
||||
model.openFileSystemAsync(entry.ref(), null, busy);
|
||||
} else if (entry.getStore() instanceof FixedHierarchyStore) {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
s.getWrapper().refreshWithChildren();
|
||||
s.getWrapper().refreshChildren();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -125,7 +121,7 @@ final class BrowserBookmarkList extends SimpleComp {
|
|||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> model.openExistingFileSystemIfPresent(null, store.asNeeded()));
|
||||
// Platform.runLater(() -> model.openExistingFileSystemIfPresent(store.asNeeded()));
|
||||
}
|
||||
};
|
||||
DROP_TIMER.schedule(activeTask, 500);
|
||||
|
|
|
@ -4,7 +4,7 @@ import atlantafx.base.controls.Breadcrumbs;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.browser.icon.DirectoryType;
|
|||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.FancyTooltipAugment;
|
||||
|
@ -252,7 +251,7 @@ public class BrowserComp extends SimpleComp {
|
|||
.bind(Bindings.createDoubleBinding(
|
||||
() -> model.getBusy().get() ? -1d : 0, PlatformThread.sync(model.getBusy())));
|
||||
|
||||
var image = DataStoreProviders.byStore(model.getStore()).getDisplayIconFileName(model.getStore());
|
||||
var image = model.getEntry().get().getProvider().getDisplayIconFileName(model.getEntry().getStore());
|
||||
var logo = PrettyImageHelper.ofFixedSquare(image, 16).createRegion();
|
||||
|
||||
tab.graphicProperty()
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.browser.icon.DirectoryType;
|
||||
import io.xpipe.app.browser.icon.FileType;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import lombok.Getter;
|
||||
|
|
|
@ -12,7 +12,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.Property;
|
||||
|
|
|
@ -2,10 +2,11 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -76,18 +77,19 @@ public class BrowserModel {
|
|||
}
|
||||
|
||||
public void restoreState(BrowserSavedState.Entry e, BooleanProperty busy) {
|
||||
var storageEntry = DataStorage.get().getStoreEntry(e.getUuid());
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
storageEntry.ifPresent(entry -> {
|
||||
openFileSystemAsync(null, entry.getStore().asNeeded(), e.getPath(), busy);
|
||||
openFileSystemAsync(entry.ref(), e.getPath(), busy);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
var list = new ArrayList<BrowserSavedState.Entry>();
|
||||
openFileSystems.forEach(model -> {
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(model.getStore());
|
||||
storageEntry.ifPresent(entry -> list.add(new BrowserSavedState.Entry(
|
||||
entry.getUuid(), model.getCurrentPath().get())));
|
||||
if (DataStorage.get().getStoreEntries().contains(model.getEntry().get())) {
|
||||
list.add(new BrowserSavedState.Entry(
|
||||
model.getEntry().get().getUuid(), model.getCurrentPath().get()));
|
||||
}
|
||||
});
|
||||
|
||||
// Don't override state if it is empty
|
||||
|
@ -138,18 +140,18 @@ public class BrowserModel {
|
|||
});
|
||||
}
|
||||
|
||||
public void openExistingFileSystemIfPresent(String name, ShellStore store) {
|
||||
public void openExistingFileSystemIfPresent(DataStoreEntryRef<? extends FileSystemStore> store) {
|
||||
var found = openFileSystems.stream()
|
||||
.filter(model -> Objects.equals(model.getStore(), store))
|
||||
.filter(model -> Objects.equals(model.getEntry(), store))
|
||||
.findFirst();
|
||||
if (found.isPresent()) {
|
||||
selected.setValue(found.get());
|
||||
} else {
|
||||
openFileSystemAsync(name, store, null, null);
|
||||
openFileSystemAsync(store, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void openFileSystemAsync(String name, ShellStore store, String path, BooleanProperty externalBusy) {
|
||||
public void openFileSystemAsync(DataStoreEntryRef<? extends FileSystemStore> store, String path, BooleanProperty externalBusy) {
|
||||
// // Prevent multiple tabs in non browser modes
|
||||
// if (!mode.equals(Mode.BROWSER)) {
|
||||
// ThreadHelper.runFailableAsync(() -> {
|
||||
|
@ -177,7 +179,7 @@ public class BrowserModel {
|
|||
// Prevent multiple calls from interfering with each other
|
||||
synchronized (BrowserModel.this) {
|
||||
try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) {
|
||||
model = new OpenFileSystemModel(name, this, store);
|
||||
model = new OpenFileSystemModel(this, store);
|
||||
model.initFileSystem();
|
||||
model.initSavedState();
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import io.xpipe.app.fxcomps.Comp;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.xpipe.app.fxcomps.impl.*;
|
|||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.geometry.Insets;
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.browser;
|
|||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
|
|
@ -54,12 +54,12 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
var storeList = new VBox();
|
||||
storeList.setSpacing(8);
|
||||
state.getLastSystems().forEach(e -> {
|
||||
var entry = DataStorage.get().getStoreEntry(e.getUuid());
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
if (entry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry.get().getState().isUsable()) {
|
||||
if (!entry.get().getValidity().isUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class BrowserWelcomeComp extends SimpleComp {
|
|||
var view = PrettyImageHelper.ofFixedSquare(graphic, 45);
|
||||
view.padding(new Insets(2, 8, 2, 8));
|
||||
var tile = new Tile(
|
||||
DataStorage.get().getStoreBrowserDisplayName(entry.get().getStore()),
|
||||
DataStorage.get().getStoreBrowserDisplayName(entry.get()),
|
||||
e.getPath(),
|
||||
view.createRegion());
|
||||
tile.setActionHandler(() -> {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
|
|
@ -3,10 +3,11 @@ package io.xpipe.app.browser;
|
|||
import io.xpipe.app.comp.base.ModalOverlayComp;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import io.xpipe.core.store.*;
|
||||
|
@ -26,7 +27,7 @@ import java.util.stream.Stream;
|
|||
@Getter
|
||||
public final class OpenFileSystemModel {
|
||||
|
||||
private final FileSystemStore store;
|
||||
private final DataStoreEntryRef<? extends FileSystemStore> entry;
|
||||
private FileSystem fileSystem;
|
||||
private final Property<String> filter = new SimpleStringProperty();
|
||||
private final BrowserFileListModel fileList;
|
||||
|
@ -42,12 +43,11 @@ public final class OpenFileSystemModel {
|
|||
private final String tooltip;
|
||||
private boolean local;
|
||||
|
||||
public OpenFileSystemModel(String name, BrowserModel browserModel, FileSystemStore store) {
|
||||
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
|
||||
this.browserModel = browserModel;
|
||||
this.store = store;
|
||||
var e = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
this.name = name != null ? name : e.isPresent() ? DataStorage.get().getStoreBrowserDisplayName(store) : "?";
|
||||
this.tooltip = e.isPresent() ? DataStorage.get().getId(e.get()).toString() : name;
|
||||
this.entry = entry;
|
||||
this.name = entry.get().getName();
|
||||
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
|
||||
this.inOverview.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return currentPath.get() == null;
|
||||
|
@ -63,7 +63,7 @@ public final class OpenFileSystemModel {
|
|||
}
|
||||
|
||||
BooleanScope.execute(busy, () -> {
|
||||
if (store instanceof ShellStore s) {
|
||||
if (entry.getStore() instanceof ShellStore s) {
|
||||
c.accept(fileSystem.getShell().orElseThrow());
|
||||
if (refresh) {
|
||||
refreshSync();
|
||||
|
@ -150,8 +150,7 @@ public final class OpenFileSystemModel {
|
|||
if (allowCommands && evaluatedPath != null && !FileNames.isAbsolute(evaluatedPath)
|
||||
&& fileSystem.getShell().isPresent()) {
|
||||
var directory = currentPath.get();
|
||||
var name = adjustedPath + " - "
|
||||
+ DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||
var name = adjustedPath + " - " + entry.get().getName();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
if (ShellDialects.ALL.stream()
|
||||
.anyMatch(dialect -> adjustedPath.startsWith(dialect.getOpenCommand()))) {
|
||||
|
@ -205,7 +204,7 @@ public final class OpenFileSystemModel {
|
|||
|
||||
private void cdSyncWithoutCheck(String path) throws Exception {
|
||||
if (fileSystem == null) {
|
||||
var fs = store.createFileSystem();
|
||||
var fs = entry.getStore().createFileSystem();
|
||||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
}
|
||||
|
@ -364,7 +363,7 @@ public final class OpenFileSystemModel {
|
|||
|
||||
public void initFileSystem() throws Exception {
|
||||
BooleanScope.execute(busy, () -> {
|
||||
var fs = store.createFileSystem();
|
||||
var fs = entry.getStore().createFileSystem();
|
||||
fs.open();
|
||||
this.fileSystem = fs;
|
||||
this.local =
|
||||
|
@ -393,23 +392,17 @@ public final class OpenFileSystemModel {
|
|||
}
|
||||
|
||||
BooleanScope.execute(busy, () -> {
|
||||
if (store instanceof ShellStore s) {
|
||||
if (entry.getStore() instanceof ShellStore s) {
|
||||
var connection = ((ConnectionFileSystem) fileSystem).getShellControl();
|
||||
var command = s.control()
|
||||
.initWith(connection.getShellDialect().getCdCommand(directory))
|
||||
.prepareTerminalOpen(directory + " - "
|
||||
+ DataStorage.get().getStoreDisplayName(store)
|
||||
.orElse("?"));
|
||||
.prepareTerminalOpen(directory + " - " + entry.get().getName());
|
||||
TerminalHelper.open(directory, command);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public OpenFileSystemHistory getHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
public void backSync() throws Exception {
|
||||
cdSyncWithoutCheck(history.back());
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -81,11 +80,7 @@ public class OpenFileSystemSavedState {
|
|||
}
|
||||
|
||||
static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) {
|
||||
var storageEntry = DataStorage.get()
|
||||
.getStoreEntryIfPresent(model.getStore())
|
||||
.map(entry -> entry.getUuid())
|
||||
.orElse(UUID.randomUUID());
|
||||
var state = AppCache.get("fs-state-" + storageEntry, OpenFileSystemSavedState.class, () -> {
|
||||
var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> {
|
||||
return new OpenFileSystemSavedState();
|
||||
});
|
||||
state.setModel(model);
|
||||
|
@ -127,8 +122,7 @@ public class OpenFileSystemSavedState {
|
|||
return;
|
||||
}
|
||||
|
||||
var storageEntry = DataStorage.get().getStoreEntryIfPresent(model.getStore());
|
||||
storageEntry.ifPresent(entry -> AppCache.update("fs-state-" + entry.getUuid(), this));
|
||||
AppCache.update("fs-state-" + model.getEntry().get().getUuid(), this);
|
||||
}
|
||||
|
||||
public void cd(String dir) {
|
||||
|
|
|
@ -4,8 +4,9 @@ import io.xpipe.app.core.AppFont;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.FileStore;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Window;
|
||||
|
@ -38,7 +39,7 @@ public class StandaloneFileBrowser {
|
|||
});
|
||||
}
|
||||
|
||||
public static void openSingleFile(Supplier<ShellStore> store, Consumer<FileStore> file) {
|
||||
public static void openSingleFile(Supplier<DataStoreEntryRef<? extends FileSystemStore>> store, Consumer<FileStore> file) {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
var model = new BrowserModel(BrowserModel.Mode.SINGLE_FILE_CHOOSER);
|
||||
var comp = new BrowserComp(model)
|
||||
|
@ -50,7 +51,7 @@ public class StandaloneFileBrowser {
|
|||
window.close();
|
||||
});
|
||||
window.show();
|
||||
model.openFileSystemAsync(null, store.get(), null, null);
|
||||
model.openFileSystemAsync(store.get(), null, null);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.app.browser.OpenFileSystemModel;
|
|||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
import lombok.Getter;
|
||||
|
|
|
@ -3,66 +3,53 @@ package io.xpipe.app.comp.base;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DropdownComp extends Comp <CompStructure<MenuButton>>{
|
||||
public class DropdownComp extends Comp<CompStructure<Button>> {
|
||||
|
||||
private final ObservableValue<String> name;
|
||||
private final ObjectProperty<Node> graphic;
|
||||
private final List<Comp<?>> items;
|
||||
|
||||
public DropdownComp(ObservableValue<String> name, List<Comp<?>> items) {
|
||||
this.name = name;
|
||||
this.graphic = new SimpleObjectProperty<>(null);
|
||||
public DropdownComp(List<Comp<?>> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public DropdownComp(ObservableValue<String> name, Node graphic, List<Comp<?>> items) {
|
||||
this.name = name;
|
||||
this.graphic = new SimpleObjectProperty<>(graphic);
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
public Node getGraphic() {
|
||||
return graphic.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> graphicProperty() {
|
||||
return graphic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<MenuButton> createBase() {
|
||||
var button = new MenuButton(null);
|
||||
if (name != null) {
|
||||
button.textProperty().bind(name);
|
||||
}
|
||||
var graphic = getGraphic();
|
||||
if (graphic instanceof FontIcon f) {
|
||||
public CompStructure<Button> createBase() {
|
||||
ContextMenu cm = new ContextMenu(items.stream()
|
||||
.map(comp -> {
|
||||
return new MenuItem(null, comp.createRegion());
|
||||
})
|
||||
.toArray(MenuItem[]::new));
|
||||
|
||||
Button button = (Button) new ButtonComp(null, () -> {})
|
||||
.apply(new ContextMenuAugment<>(e -> true, () -> {
|
||||
return cm;
|
||||
}))
|
||||
.createRegion();
|
||||
|
||||
button.visibleProperty()
|
||||
.bind(BindingsHelper.anyMatch(cm.getItems().stream()
|
||||
.map(menuItem -> menuItem.getGraphic().visibleProperty())
|
||||
.toList()));
|
||||
|
||||
var graphic = new FontIcon("mdi2c-chevron-double-down");
|
||||
SimpleChangeListener.apply(button.fontProperty(), c -> {
|
||||
f.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
||||
graphic.setIconSize((int) new Size(c.getSize(), SizeUnits.PT).pixels());
|
||||
});
|
||||
}
|
||||
|
||||
button.setGraphic(getGraphic());
|
||||
button.setGraphic(graphic);
|
||||
button.getStyleClass().add("dropdown-comp");
|
||||
|
||||
items.forEach(comp -> {
|
||||
var i = new MenuItem(null,comp.createRegion());
|
||||
button.getItems().add(i);
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(button);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import io.xpipe.app.ext.DownloadModuleInstall;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.util.DynamicOptionsBuilder;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.Priority;
|
||||
|
@ -27,7 +27,7 @@ public class InstallExtensionComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var builder = new DynamicOptionsBuilder(false);
|
||||
var builder = new OptionsBuilder();
|
||||
builder.addTitle("installRequired");
|
||||
var header = new LabelComp(AppI18n.observable("extensionInstallDescription"))
|
||||
.apply(struc -> struc.get().setWrapText(true));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.IconButtonComp;
|
||||
|
@ -58,6 +59,8 @@ public class IntegratedTextAreaComp extends SimpleComp {
|
|||
(s) -> {
|
||||
Platform.runLater(() -> value.setValue(s));
|
||||
}))
|
||||
.styleClass("edit-button")
|
||||
.apply(struc -> struc.get().getStyleClass().remove(Styles.FLAT))
|
||||
.createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,11 +67,13 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
|||
var newShown = shown.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, compFunction.apply(v).createRegion());
|
||||
var comp = compFunction.apply(v);
|
||||
cache.put(v, comp != null ? comp.createRegion() : null);
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.toList();
|
||||
|
||||
for (int i = 0; i < newShown.size(); i++) {
|
||||
|
|
|
@ -29,7 +29,9 @@ public class NamedToggleComp extends SimpleComp {
|
|||
s.setSelected(newValue);
|
||||
});
|
||||
});
|
||||
if (name != null) {
|
||||
s.textProperty().bind(PlatformThread.sync(name));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,12 @@ import io.xpipe.app.core.AppResources;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.nio.file.Files;
|
||||
|
@ -18,20 +21,38 @@ import java.util.Map;
|
|||
public class OsLogoComp extends SimpleComp {
|
||||
|
||||
private final StoreEntryWrapper wrapper;
|
||||
private final ObservableValue<SystemStateComp.State> state;
|
||||
|
||||
public OsLogoComp(StoreEntryWrapper wrapper) {
|
||||
this(wrapper, new SimpleObjectProperty<>(SystemStateComp.State.SUCCESS));
|
||||
}
|
||||
|
||||
public OsLogoComp(StoreEntryWrapper wrapper, ObservableValue<SystemStateComp.State> state) {
|
||||
this.wrapper = wrapper;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var img = Bindings.createObjectBinding(
|
||||
var img = BindingsHelper.persist(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return wrapper.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? getImage(wrapper.getInformation().get()) : null;
|
||||
if (state.getValue() != SystemStateComp.State.SUCCESS) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var ps = wrapper.getPersistentState().getValue();
|
||||
if (!(ps instanceof ShellStoreState sss)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getImage(sss.getOsName());
|
||||
},
|
||||
wrapper.getState(), wrapper.getInformation());
|
||||
return new StackComp(List.of(new SystemStateComp(wrapper).hide(img.isNotNull()), PrettyImageHelper.ofSvg(img, 24, 24))).createRegion();
|
||||
wrapper.getPersistentState(), state));
|
||||
var hide = BindingsHelper.map(img, s -> s != null);
|
||||
return new StackComp(List.of(
|
||||
new SystemStateComp(state).hide(hide),
|
||||
PrettyImageHelper.ofSvg(img, 24, 24).visible(hide)))
|
||||
.createRegion();
|
||||
}
|
||||
|
||||
private static final Map<String, String> ICONS = new HashMap<>();
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.core.AppI18n;
|
|||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -28,18 +29,22 @@ public class StoreToggleComp extends SimpleComp {
|
|||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var disable = section.getWrapper().getState().map(state -> state != DataStoreEntry.State.COMPLETE_AND_VALID);
|
||||
var disable = section.getWrapper().getValidity().map(state -> state != DataStoreEntry.Validity.COMPLETE);
|
||||
var visible = BindingsHelper.persist(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return section.getWrapper().getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
return section.getWrapper().getValidity().getValue() == DataStoreEntry.Validity.COMPLETE
|
||||
&& section.getShowDetails().get();
|
||||
},
|
||||
section.getWrapper().getState(),
|
||||
section.getWrapper().getValidity(),
|
||||
section.getShowDetails()));
|
||||
var t = new NamedToggleComp(value, AppI18n.observable(nameKey))
|
||||
.visible(visible)
|
||||
.disable(disable);
|
||||
value.addListener((observable, oldValue, newValue) -> onChange.accept(newValue));
|
||||
value.addListener((observable, oldValue, newValue) -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
onChange.accept(newValue);
|
||||
});
|
||||
});
|
||||
return t.createRegion();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,16 +3,19 @@ package io.xpipe.app.comp.base;
|
|||
import atlantafx.base.theme.Styles;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.Getter;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
import org.kordamp.ikonli.javafx.StackedFontIcon;
|
||||
|
||||
@Getter
|
||||
public class SystemStateComp extends SimpleComp {
|
||||
|
||||
|
||||
|
@ -23,23 +26,21 @@ public class SystemStateComp extends SimpleComp {
|
|||
public enum State {
|
||||
FAILURE,
|
||||
SUCCESS,
|
||||
OTHER
|
||||
OTHER;
|
||||
|
||||
public static ObservableValue<State> shellState(StoreEntryWrapper w) {
|
||||
return BindingsHelper.map(w.getPersistentState(),o -> {
|
||||
if (o instanceof ShellStoreState shellStoreState) {
|
||||
return shellStoreState.getRunning() != null ? shellStoreState.getRunning() ? SUCCESS : FAILURE : OTHER;
|
||||
}
|
||||
|
||||
return OTHER;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final ObservableValue<State> state;
|
||||
|
||||
public SystemStateComp(StoreEntryWrapper w) {
|
||||
this.state = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID
|
||||
? State.FAILURE
|
||||
: w.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? State.SUCCESS
|
||||
: State.OTHER;
|
||||
},
|
||||
w.getState());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var icon = PlatformThread.sync(Bindings.createStringBinding(
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
package io.xpipe.app.comp.storage;
|
||||
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataFlow;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DataSourceTypeComp extends SimpleComp {
|
||||
|
||||
public static final Map<DataSourceType, String> ICONS = Map.of(
|
||||
DataSourceType.TABLE, "mdi2t-table-large",
|
||||
DataSourceType.STRUCTURE, "mdi2b-beaker-outline",
|
||||
DataSourceType.TEXT, "mdi2t-text-box",
|
||||
DataSourceType.RAW, "mdi2c-card-outline",
|
||||
DataSourceType.COLLECTION, "mdi2b-briefcase-outline");
|
||||
private static final String MISSING_ICON = "mdi2c-comment-question-outline";
|
||||
private static final Color MISSING_COLOR = Color.RED;
|
||||
private static final Map<DataSourceType, Color> COLORS = Map.of(
|
||||
DataSourceType.TABLE, Color.rgb(0, 160, 0, 0.5),
|
||||
DataSourceType.STRUCTURE, Color.ORANGERED,
|
||||
DataSourceType.TEXT, Color.LIGHTBLUE,
|
||||
DataSourceType.RAW, Color.GREY,
|
||||
DataSourceType.COLLECTION, Color.ORCHID.deriveColor(0, 1.0, 0.85, 1.0));
|
||||
private final DataSourceType type;
|
||||
private final DataFlow flow;
|
||||
|
||||
public DataSourceTypeComp(DataSourceType type, DataFlow flow) {
|
||||
this.type = type;
|
||||
this.flow = flow;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var bg = new Region();
|
||||
bg.setBackground(new Background(new BackgroundFill(
|
||||
type != null ? COLORS.get(type) : MISSING_COLOR, new CornerRadii(12), Insets.EMPTY)));
|
||||
bg.getStyleClass().add("background");
|
||||
|
||||
var sp = new StackPane(bg);
|
||||
sp.setAlignment(Pos.CENTER);
|
||||
sp.getStyleClass().add("data-source-type-comp");
|
||||
|
||||
var icon = new FontIcon(type != null ? ICONS.get(type) : MISSING_ICON);
|
||||
icon.iconSizeProperty().bind(Bindings.divide(sp.heightProperty(), 2));
|
||||
sp.getChildren().add(icon);
|
||||
|
||||
if (flow == DataFlow.INPUT || flow == DataFlow.INPUT_OUTPUT) {
|
||||
var flowIcon = createInputFlowType();
|
||||
sp.getChildren().add(flowIcon);
|
||||
}
|
||||
|
||||
if (flow == DataFlow.OUTPUT || flow == DataFlow.INPUT_OUTPUT) {
|
||||
var flowIcon = createOutputFlowType();
|
||||
sp.getChildren().add(flowIcon);
|
||||
}
|
||||
|
||||
if (flow == DataFlow.TRANSFORMER) {
|
||||
var flowIcon = createTransformerFlowType();
|
||||
sp.getChildren().add(flowIcon);
|
||||
}
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
||||
private Region createInputFlowType() {
|
||||
var icon = new FontIcon("mdi2c-chevron-double-left");
|
||||
icon.setIconColor(Color.WHITE);
|
||||
var anchorPane = new AnchorPane(icon);
|
||||
AnchorPane.setLeftAnchor(icon, 3.0);
|
||||
AnchorPane.setBottomAnchor(icon, 3.0);
|
||||
return anchorPane;
|
||||
}
|
||||
|
||||
private Region createOutputFlowType() {
|
||||
var icon = new FontIcon("mdi2c-chevron-double-right");
|
||||
icon.setIconColor(Color.WHITE);
|
||||
var anchorPane = new AnchorPane(icon);
|
||||
AnchorPane.setRightAnchor(icon, 3.0);
|
||||
AnchorPane.setBottomAnchor(icon, 3.0);
|
||||
return anchorPane;
|
||||
}
|
||||
|
||||
private Region createTransformerFlowType() {
|
||||
var icon = new FontIcon("mdi2t-transfer");
|
||||
icon.setIconColor(Color.WHITE);
|
||||
var anchorPane = new AnchorPane(icon);
|
||||
AnchorPane.setRightAnchor(icon, 3.0);
|
||||
AnchorPane.setBottomAnchor(icon, 3.0);
|
||||
return anchorPane;
|
||||
}
|
||||
}
|
|
@ -31,11 +31,13 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||
: Comp.empty();
|
||||
information.setGraphic(state.createRegion());
|
||||
|
||||
var summary = wrapper.summary();
|
||||
var info = wrapper.getEntry().getProvider().informationString(wrapper);
|
||||
SimpleChangeListener.apply(grid.hoverProperty(), val -> {
|
||||
if (val && wrapper.getSummary().get() != null && wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
||||
information.textProperty().bind(PlatformThread.sync(wrapper.getSummary()));
|
||||
if (val && summary.getValue() != null && wrapper.getEntry().getProvider().alwaysShowSummary()) {
|
||||
information.textProperty().bind(PlatformThread.sync(summary));
|
||||
} else {
|
||||
information.textProperty().bind(PlatformThread.sync(wrapper.getInformation()));
|
||||
information.textProperty().bind(PlatformThread.sync(info));
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -56,7 +58,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
|||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
}
|
||||
|
||||
var customSize = content != null ? 300 : 0;
|
||||
var customSize = content != null ? 200 : 0;
|
||||
var custom = new ColumnConstraints(0, customSize, customSize);
|
||||
custom.setHalignment(HPos.RIGHT);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
|||
info.setHalignment(HPos.LEFT);
|
||||
grid.getColumnConstraints().add(info);
|
||||
|
||||
var customSize = content != null ? 300 : 0;
|
||||
var customSize = content != null ? 200 : 0;
|
||||
var custom = new ColumnConstraints(0, customSize, customSize);
|
||||
custom.setHalignment(HPos.RIGHT);
|
||||
var cr = content != null ? content.createRegion() : new Region();
|
||||
|
|
|
@ -31,7 +31,7 @@ public class StoreCreationMenu {
|
|||
host.textProperty().bind(AppI18n.observable("addHost"));
|
||||
host.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(DataStoreProviders.byName("ssh").orElseThrow(),
|
||||
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.HOST));
|
||||
v -> DataStoreProvider.CreationCategory.HOST.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(host);
|
||||
|
@ -42,7 +42,7 @@ public class StoreCreationMenu {
|
|||
shell.textProperty().bind(AppI18n.observable("addShell"));
|
||||
shell.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(null,
|
||||
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.SHELL));
|
||||
v -> DataStoreProvider.CreationCategory.SHELL.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(shell);
|
||||
|
@ -53,7 +53,7 @@ public class StoreCreationMenu {
|
|||
cmd.textProperty().bind(AppI18n.observable("addCommand"));
|
||||
cmd.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(null,
|
||||
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.COMMAND));
|
||||
v -> DataStoreProvider.CreationCategory.COMMAND.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(cmd);
|
||||
|
@ -64,7 +64,7 @@ public class StoreCreationMenu {
|
|||
db.textProperty().bind(AppI18n.observable("addDatabase"));
|
||||
db.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(null,
|
||||
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.DATABASE));
|
||||
v -> DataStoreProvider.CreationCategory.DATABASE.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(db);
|
||||
|
@ -75,11 +75,22 @@ public class StoreCreationMenu {
|
|||
tunnel.textProperty().bind(AppI18n.observable("addTunnel"));
|
||||
tunnel.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(null,
|
||||
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.TUNNEL));
|
||||
v -> DataStoreProvider.CreationCategory.TUNNEL.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(tunnel);
|
||||
}
|
||||
{
|
||||
var script = new MenuItem();
|
||||
script.setGraphic(new FontIcon("mdi2s-script-text-outline"));
|
||||
script.textProperty().bind(AppI18n.observable("addScript"));
|
||||
script.setOnAction(event -> {
|
||||
GuiDsStoreCreator.showCreation(null,
|
||||
v -> DataStoreProvider.CreationCategory.SCRIPT.equals(v.getCreationCategory()));
|
||||
event.consume();
|
||||
});
|
||||
menu.getItems().add(script);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.DesktopShortcuts;
|
||||
|
@ -70,7 +69,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
public static final ObservableDoubleValue INFO_NO_CONTENT_WIDTH =
|
||||
App.getApp().getStage().widthProperty().divide(2.2);
|
||||
public static final ObservableDoubleValue INFO_WITH_CONTENT_WIDTH =
|
||||
App.getApp().getStage().widthProperty().divide(2.2).add(-300);
|
||||
App.getApp().getStage().widthProperty().divide(2.2).add(-200);
|
||||
protected final StoreEntryWrapper wrapper;
|
||||
protected final Comp<?> content;
|
||||
|
||||
|
@ -91,12 +90,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
button.setMaxWidth(3000);
|
||||
button.setFocusTraversable(true);
|
||||
button.accessibleTextProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return wrapper.getName();
|
||||
},
|
||||
wrapper.nameProperty()));
|
||||
button.accessibleHelpProperty().bind(wrapper.getInformation());
|
||||
.bind(wrapper.nameProperty());
|
||||
button.setOnAction(event -> {
|
||||
event.consume();
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
|
@ -117,7 +111,8 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
protected Label createInformation() {
|
||||
var information = new Label();
|
||||
information.setGraphicTextGap(7);
|
||||
information.textProperty().bind(PlatformThread.sync(wrapper.getInformation()));
|
||||
information.textProperty().bind(wrapper.getEntry().getProvider() != null ?
|
||||
PlatformThread.sync(wrapper.getEntry().getProvider().informationString(wrapper)) : new SimpleStringProperty());
|
||||
information.getStyleClass().add("information");
|
||||
AppFont.header(information);
|
||||
|
||||
|
@ -131,14 +126,14 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
|
||||
protected Label createSummary() {
|
||||
var summary = new Label();
|
||||
summary.textProperty().bind(PlatformThread.sync(wrapper.getSummary()));
|
||||
summary.textProperty().bind(wrapper.summary());
|
||||
summary.getStyleClass().add("summary");
|
||||
AppFont.small(summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
protected void applyState(Node node) {
|
||||
SimpleChangeListener.apply(PlatformThread.sync(wrapper.getState()), val -> {
|
||||
SimpleChangeListener.apply(PlatformThread.sync(wrapper.getValidity()), val -> {
|
||||
switch (val) {
|
||||
case LOAD_FAILED -> {
|
||||
node.pseudoClassStateChanged(FAILED, true);
|
||||
|
@ -157,24 +152,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
}
|
||||
|
||||
protected Comp<?> createName() {
|
||||
// var filtered = BindingsHelper.filteredContentBinding(
|
||||
// StoreViewState.get().getAllEntries(),
|
||||
// other -> other.getEntry().getState().isUsable()
|
||||
// && entry.getEntry()
|
||||
// .getStore()
|
||||
// .equals(other.getEntry()
|
||||
// .getProvider()
|
||||
// .getLogicalParent(other.getEntry().getStore())));
|
||||
LabelComp name = new LabelComp(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return wrapper.getName();
|
||||
// + (filtered.size() > 0 && entry.getEntry().getStore() instanceof
|
||||
// FixedHierarchyStore
|
||||
// ? " (" + filtered.size() + ")"
|
||||
// : "");
|
||||
},
|
||||
wrapper.nameProperty(),
|
||||
wrapper.getInformation()));
|
||||
LabelComp name = new LabelComp(wrapper.nameProperty());
|
||||
name.apply(struc -> struc.get().setTextOverrun(OverrunStyle.CENTER_ELLIPSIS))
|
||||
.apply(struc -> struc.get().setPadding(new Insets(5, 5, 5, 0)));
|
||||
name.apply(s -> AppFont.header(s.get()));
|
||||
|
@ -183,14 +161,14 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
}
|
||||
|
||||
protected Node createIcon(int w, int h) {
|
||||
var img = wrapper.isDisabled()
|
||||
var img = wrapper.disabledProperty().get()
|
||||
? "disabled_icon.png"
|
||||
: wrapper.getEntry()
|
||||
.getProvider()
|
||||
.getDisplayIconFileName(wrapper.getEntry().getStore());
|
||||
var imageComp = PrettyImageHelper.ofFixed(img, w, h);
|
||||
var storeIcon = imageComp.createRegion();
|
||||
if (wrapper.getState().getValue().isUsable()) {
|
||||
if (wrapper.getValidity().getValue().isUsable()) {
|
||||
new FancyTooltipAugment<>(new SimpleStringProperty(
|
||||
wrapper.getEntry().getProvider().getDisplayName()))
|
||||
.augment(storeIcon);
|
||||
|
@ -319,7 +297,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
sc.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
DesktopShortcuts.create(url,
|
||||
wrapper.getName() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().getStore().asNeeded()).getValue() + ")");
|
||||
wrapper.nameProperty().getValue() + " (" + p.getKey().getDataStoreCallSite().getName(wrapper.getEntry().getStore().asNeeded()).getValue() + ")");
|
||||
});
|
||||
});
|
||||
menu.getItems().add(sc);
|
||||
|
@ -361,12 +339,6 @@ public abstract class StoreEntryComp extends SimpleComp {
|
|||
});
|
||||
contextMenu.getItems().add(move);
|
||||
|
||||
var refresh = new MenuItem(AppI18n.get("refresh"), new FontIcon("mdal-360"));
|
||||
refresh.setOnAction(event -> {
|
||||
DataStorage.get().refreshAsync(wrapper.getEntry(), true);
|
||||
});
|
||||
contextMenu.getItems().add(refresh);
|
||||
|
||||
var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline"));
|
||||
del.disableProperty().bind(wrapper.getDeletable().not());
|
||||
del.setOnAction(event -> wrapper.delete());
|
||||
|
|
|
@ -9,9 +9,9 @@ import io.xpipe.app.storage.DataStorage;
|
|||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.time.Duration;
|
||||
|
@ -29,16 +29,13 @@ public class StoreEntryWrapper {
|
|||
private final BooleanProperty disabled = new SimpleBooleanProperty();
|
||||
private final BooleanProperty inRefresh = new SimpleBooleanProperty();
|
||||
private final BooleanProperty observing = new SimpleBooleanProperty();
|
||||
private final Property<DataStoreEntry.State> state = new SimpleObjectProperty<>();
|
||||
private final StringProperty information = new SimpleStringProperty();
|
||||
private final StringProperty summary = new SimpleStringProperty();
|
||||
private final Property<DataStoreEntry.Validity> validity = new SimpleObjectProperty<>();
|
||||
private final Map<ActionProvider, BooleanProperty> actionProviders;
|
||||
private final Property<ActionProvider.DefaultDataStoreCallSite<?>> defaultActionProvider;
|
||||
private final BooleanProperty deletable = new SimpleBooleanProperty();
|
||||
private final BooleanProperty expanded = new SimpleBooleanProperty();
|
||||
private final Property<StoreCategoryWrapper> category = new SimpleObjectProperty<>();
|
||||
private final Property<StoreEntryWrapper> displayParent = new SimpleObjectProperty<>();
|
||||
private final IntegerProperty depth = new SimpleIntegerProperty();
|
||||
private final Property<Object> persistentState = new SimpleObjectProperty<>();
|
||||
|
||||
public StoreEntryWrapper(DataStoreEntry entry) {
|
||||
this.entry = entry;
|
||||
|
@ -54,7 +51,8 @@ public class StoreEntryWrapper {
|
|||
.getApplicableClass()
|
||||
.isAssignableFrom(entry.getStore().getClass());
|
||||
})
|
||||
.sorted(Comparator.comparing(actionProvider -> actionProvider.getDataStoreCallSite().isSystemAction()))
|
||||
.sorted(Comparator.comparing(
|
||||
actionProvider -> actionProvider.getDataStoreCallSite().isSystemAction()))
|
||||
.forEach(dataStoreActionProvider -> {
|
||||
actionProviders.put(dataStoreActionProvider, new SimpleBooleanProperty(true));
|
||||
});
|
||||
|
@ -74,7 +72,7 @@ public class StoreEntryWrapper {
|
|||
return null;
|
||||
}
|
||||
|
||||
var p = DataStorage.get().getParent(entry, true).orElse(null);
|
||||
var p = DataStorage.get().getDisplayParent(entry).orElse(null);
|
||||
return StoreViewState.get().getAllEntries().stream()
|
||||
.filter(storeEntryWrapper -> storeEntryWrapper.getEntry().equals(p))
|
||||
.findFirst()
|
||||
|
@ -96,6 +94,24 @@ public class StoreEntryWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
public ObservableValue<String> summary() {
|
||||
return PlatformThread.sync(Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (!validity.getValue().isUsable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return entry.getProvider().summaryString(this);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
return null;
|
||||
}
|
||||
},
|
||||
validity,
|
||||
persistentState));
|
||||
}
|
||||
|
||||
private void setupListeners() {
|
||||
name.addListener((c, o, n) -> {
|
||||
entry.setName(n);
|
||||
|
@ -124,31 +140,15 @@ public class StoreEntryWrapper {
|
|||
|
||||
lastAccess.setValue(entry.getLastAccess());
|
||||
disabled.setValue(entry.isDisabled());
|
||||
state.setValue(entry.getState());
|
||||
validity.setValue(entry.getValidity());
|
||||
expanded.setValue(entry.isExpanded());
|
||||
observing.setValue(entry.isObserving());
|
||||
information.setValue(entry.getInformation());
|
||||
displayParent.setValue(computeDisplayParent());
|
||||
persistentState.setValue(entry.getStorePersistentState());
|
||||
|
||||
inRefresh.setValue(entry.isInRefresh());
|
||||
if (entry.getState().isUsable()) {
|
||||
try {
|
||||
summary.setValue(entry.getProvider().toSummaryString(entry.getStore(), 50));
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
}
|
||||
|
||||
deletable.setValue(entry.getConfiguration().isDeletable()
|
||||
|| AppPrefs.get().developerDisableGuiRestrictions().getValue());
|
||||
|
||||
var d = 0;
|
||||
var c = this;
|
||||
while ((c = c.getDisplayParent().getValue()) != null) {
|
||||
d++;
|
||||
}
|
||||
depth.setValue(d);
|
||||
|
||||
actionProviders.keySet().forEach(dataStoreActionProvider -> {
|
||||
if (!isInStorage()) {
|
||||
actionProviders.get(dataStoreActionProvider).set(false);
|
||||
|
@ -156,7 +156,7 @@ public class StoreEntryWrapper {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!entry.getState().isUsable()
|
||||
if (!entry.getValidity().isUsable()
|
||||
&& !dataStoreActionProvider
|
||||
.getDataStoreCallSite()
|
||||
.activeType()
|
||||
|
@ -194,51 +194,20 @@ public class StoreEntryWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
public void refreshIfNeeded() throws Exception {
|
||||
if (entry.getState().equals(DataStoreEntry.State.COMPLETE_BUT_INVALID)
|
||||
|| entry.getState().equals(DataStoreEntry.State.COMPLETE_NOT_VALIDATED)) {
|
||||
getEntry().refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshAsync() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
getEntry().refresh(true);
|
||||
});
|
||||
}
|
||||
|
||||
public void refreshWithChildren() throws Exception {
|
||||
getEntry().refresh(true);
|
||||
public void refreshChildren() {
|
||||
var hasChildren = DataStorage.get().refreshChildren(entry);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
expanded.set(hasChildren);
|
||||
});
|
||||
}
|
||||
|
||||
public void refreshWithChildrenAsync() {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
refreshWithChildren();
|
||||
});
|
||||
}
|
||||
|
||||
public void mutateAsync(DataStore newValue) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
var hasChildren = DataStorage.get().setAndRefresh(getEntry(), newValue);
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
expanded.set(hasChildren);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void executeDefaultAction() throws Exception {
|
||||
var found = getDefaultActionProvider().getValue();
|
||||
entry.updateLastUsed();
|
||||
if (found != null) {
|
||||
refreshIfNeeded();
|
||||
found.createAction(entry.getStore().asNeeded()).execute();
|
||||
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
|
||||
refreshWithChildrenAsync();
|
||||
} else {
|
||||
entry.setExpanded(!entry.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,36 +216,13 @@ public class StoreEntryWrapper {
|
|||
}
|
||||
|
||||
public boolean shouldShow(String filter) {
|
||||
return filter == null
|
||||
|| getName().toLowerCase().contains(filter.toLowerCase())
|
||||
|| (summary.get() != null && summary.get().toLowerCase().contains(filter.toLowerCase()))
|
||||
|| (information.get() != null && information.get().toLowerCase().contains(filter.toLowerCase()));
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.getValue();
|
||||
return filter == null || nameProperty().getValue().toLowerCase().contains(filter.toLowerCase());
|
||||
}
|
||||
|
||||
public Property<String> nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DataStoreEntry getEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
public Instant getLastAccess() {
|
||||
return lastAccess.getValue();
|
||||
}
|
||||
|
||||
public Property<Instant> lastAccessProperty() {
|
||||
return lastAccess;
|
||||
}
|
||||
|
||||
public boolean isDisabled() {
|
||||
return disabled.get();
|
||||
}
|
||||
|
||||
public BooleanProperty disabledProperty() {
|
||||
return disabled;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
|||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.ScanAlert;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -38,7 +37,7 @@ public class StoreIntroComp extends SimpleComp {
|
|||
});
|
||||
|
||||
var scanButton = new Button(AppI18n.get("detectConnections"), new FontIcon("mdi2m-magnify"));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().getStoreEntry(new LocalStore())));
|
||||
scanButton.setOnAction(event -> ScanAlert.showAsync(DataStorage.get().local()));
|
||||
var scanPane = new StackPane(scanButton);
|
||||
scanPane.setAlignment(Pos.CENTER);
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ public class StoreSection {
|
|||
private static ObservableList<StoreSection> sorted(
|
||||
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
|
||||
var c = Comparator.<StoreSection>comparingInt(
|
||||
value -> value.getWrapper().getEntry().getState().isUsable() ? 1 : -1);
|
||||
value -> value.getWrapper().getEntry().getValidity().isUsable() ? 1 : -1);
|
||||
category.getValue().getSortMode().addListener((observable, oldValue, newValue) -> {
|
||||
int a = 0;
|
||||
});
|
||||
|
@ -109,12 +109,12 @@ public class StoreSection {
|
|||
ordered,
|
||||
section -> {
|
||||
var noParent = DataStorage.get()
|
||||
.getParent(section.getWrapper().getEntry(), true)
|
||||
.getDisplayParent(section.getWrapper().getEntry())
|
||||
.isEmpty();
|
||||
var sameCategory =
|
||||
category.getValue().contains(section.getWrapper().getEntry());
|
||||
var diffParentCategory = DataStorage.get()
|
||||
.getParent(section.getWrapper().getEntry(), true)
|
||||
.getDisplayParent(section.getWrapper().getEntry())
|
||||
.map(entry -> !category.getValue().contains(entry))
|
||||
.orElse(false);
|
||||
var showFilter = section.shouldShow(filterString.get());
|
||||
|
@ -133,13 +133,13 @@ public class StoreSection {
|
|||
Predicate<StoreEntryWrapper> entryFilter,
|
||||
ObservableStringValue filterString,
|
||||
ObservableValue<StoreCategoryWrapper> category) {
|
||||
if (e.getEntry().getState() == DataStoreEntry.State.LOAD_FAILED) {
|
||||
if (e.getEntry().getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return new StoreSection(e, FXCollections.observableArrayList(), FXCollections.observableArrayList(), depth);
|
||||
}
|
||||
|
||||
var allChildren = BindingsHelper.filteredContentBinding(all, other -> {
|
||||
return DataStorage.get()
|
||||
.getParent(other.getEntry(), true)
|
||||
.getDisplayParent(other.getEntry())
|
||||
.map(found -> found.equals(e.getEntry()))
|
||||
.orElse(false);
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ public interface StoreSortMode {
|
|||
@Override
|
||||
public Comparator<StoreSection> comparator() {
|
||||
return Comparator.<StoreSection, String>comparing(
|
||||
e -> e.getWrapper().getName().toLowerCase(Locale.ROOT));
|
||||
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ public interface StoreSortMode {
|
|||
@Override
|
||||
public Comparator<StoreSection> comparator() {
|
||||
return Comparator.<StoreSection, String>comparing(
|
||||
e -> e.getWrapper().getName().toLowerCase(Locale.ROOT))
|
||||
e -> e.getWrapper().nameProperty().getValue().toLowerCase(Locale.ROOT))
|
||||
.reversed();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -89,6 +89,15 @@ public class StoreViewState {
|
|||
.orElseThrow();
|
||||
}
|
||||
|
||||
|
||||
public StoreCategoryWrapper getScriptsCategory() {
|
||||
return categories.stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.SCRIPTS_CATEGORY_UUID))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
new StoreViewState();
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public class DsStoreProviderChoiceComp extends Comp<CompStructure<ComboBox<Node>
|
|||
var comboBox = new CustomComboBoxBuilder<>(provider, this::createGraphic, createDefaultNode(), v -> true);
|
||||
comboBox.setAccessibleNames(dataStoreProvider -> dataStoreProvider.getDisplayName());
|
||||
var l = getProviders().stream()
|
||||
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.canManuallyCreate() || staticDisplay).toList();
|
||||
.filter(p -> AppPrefs.get().developerShowHiddenProviders().get() || p.getCreationCategory() != null || staticDisplay).toList();
|
||||
l
|
||||
.forEach(comboBox::add);
|
||||
if (l.size() == 1) {
|
||||
|
|
|
@ -109,7 +109,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
|
|||
newE -> {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
if (!DataStorage.get().getStoreEntries().contains(e)) {
|
||||
DataStorage.get().addStoreEntry(newE);
|
||||
DataStorage.get().addStoreEntryIfNotPresent(newE);
|
||||
} else {
|
||||
DataStorage.get().updateEntry(e, newE);
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
|
|||
filter,
|
||||
e -> {
|
||||
try {
|
||||
DataStorage.get().addStoreEntry(e);
|
||||
DataStorage.get().addStoreEntryIfNotPresent(e);
|
||||
if (e.getProvider().shouldHaveChildren()) {
|
||||
ScanAlert.showAsync(e);
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
|
|||
// return;
|
||||
// }
|
||||
|
||||
var d = n.guiDialog(store);
|
||||
var d = n.guiDialog(entry.getValue(), store);
|
||||
var propVal = new SimpleValidator();
|
||||
var propR = createStoreProperties(d == null || d.getComp() == null ? null : d.getComp(), propVal);
|
||||
|
||||
|
@ -349,7 +349,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
|
|||
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try (var b = new BooleanScope(busy).start()) {
|
||||
entry.getValue().refresh(true);
|
||||
entry.getValue().validateOrThrow();
|
||||
finished.setValue(true);
|
||||
PlatformThread.runLaterIfNeeded(parent::next);
|
||||
} catch (Exception ex) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.comp.AppLayoutComp;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import javafx.application.Application;
|
||||
|
@ -29,6 +30,7 @@ public class App extends Application {
|
|||
TrackEvent.info("Application launched");
|
||||
APP = this;
|
||||
stage = primaryStage;
|
||||
stage.opacityProperty().bind(AppPrefs.get().windowOpacity());
|
||||
|
||||
// Set dock icon explicitly on mac
|
||||
// This is necessary in case XPipe was started through a script as it will have no icon otherwise
|
||||
|
|
|
@ -55,7 +55,7 @@ public class AppCache {
|
|||
return notPresent.get();
|
||||
}
|
||||
|
||||
return (T) JacksonMapper.newMapper().treeToValue(tree, type);
|
||||
return (T) JacksonMapper.getDefault().treeToValue(tree, type);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
FileUtils.deleteQuietly(path.toFile());
|
||||
|
@ -69,7 +69,7 @@ public class AppCache {
|
|||
|
||||
try {
|
||||
FileUtils.forceMkdirParent(path.toFile());
|
||||
var tree = JacksonMapper.newMapper().valueToTree(val);
|
||||
var tree = JacksonMapper.getDefault().valueToTree(val);
|
||||
JsonConfigHelper.writeConfig(path, tree);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable("Could not parse cached data for key " + key, e)
|
||||
|
|
|
@ -42,12 +42,16 @@ public class AppExtensionManager {
|
|||
INSTANCE.determineExtensionDirectories();
|
||||
INSTANCE.loadBaseExtension();
|
||||
INSTANCE.loadAllExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
if (load) {
|
||||
INSTANCE.addNativeLibrariesToPath();
|
||||
try {
|
||||
XPipeServiceProviders.load(INSTANCE.extendedLayer);
|
||||
MessageExchangeImpls.loadAll();
|
||||
} catch (Throwable t) {
|
||||
throw new ExtensionException("Service provider initialization failed. Is the installation data corrupt?", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import io.xpipe.app.issue.ErrorEvent;
|
|||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.SupportedLocale;
|
||||
import io.xpipe.app.util.DynamicOptionsBuilder;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.app.util.Translatable;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
|
@ -149,8 +148,7 @@ public class AppI18n {
|
|||
|| caller.equals(FancyTooltipAugment.class)
|
||||
|| caller.equals(PrefsChoiceValue.class)
|
||||
|| caller.equals(Translatable.class)
|
||||
|| caller.equals(OptionsBuilder.class)
|
||||
|| caller.equals(DynamicOptionsBuilder.class)) {
|
||||
|| caller.equals(OptionsBuilder.class)) {
|
||||
continue;
|
||||
}
|
||||
var split = caller.getModule().getName().split("\\.");
|
||||
|
|
|
@ -117,7 +117,7 @@ public class AppSocketServer {
|
|||
|
||||
JsonNode node;
|
||||
try (InputStream blockIn = BeaconFormat.readBlocks(clientSocket.getInputStream())) {
|
||||
node = JacksonMapper.newMapper().readTree(blockIn);
|
||||
node = JacksonMapper.getDefault().readTree(blockIn);
|
||||
}
|
||||
if (node.isMissingNode()) {
|
||||
TrackEvent.trace("beacon", "Received EOF");
|
||||
|
@ -183,14 +183,14 @@ public class AppSocketServer {
|
|||
try {
|
||||
JsonNode informationNode;
|
||||
try (InputStream blockIn = BeaconFormat.readBlocks(clientSocket.getInputStream())) {
|
||||
informationNode = JacksonMapper.newMapper().readTree(blockIn);
|
||||
informationNode = JacksonMapper.getDefault().readTree(blockIn);
|
||||
}
|
||||
if (informationNode.isMissingNode()) {
|
||||
TrackEvent.trace("beacon", "Received EOF");
|
||||
return;
|
||||
}
|
||||
var information =
|
||||
JacksonMapper.newMapper().treeToValue(informationNode, BeaconClient.ClientInformation.class);
|
||||
JacksonMapper.getDefault().treeToValue(informationNode, BeaconClient.ClientInformation.class);
|
||||
|
||||
TrackEvent.builder()
|
||||
.category("beacon")
|
||||
|
@ -276,7 +276,7 @@ public class AppSocketServer {
|
|||
}
|
||||
|
||||
public <T extends ResponseMessage> void sendResponse(Socket outSocket, T obj) throws Exception {
|
||||
ObjectNode json = JacksonMapper.newMapper().valueToTree(obj);
|
||||
ObjectNode json = JacksonMapper.getDefault().valueToTree(obj);
|
||||
var prov = MessageExchanges.byResponse(obj).get();
|
||||
json.set("messageType", new TextNode(prov.getId()));
|
||||
json.set("messagePhase", new TextNode("response"));
|
||||
|
@ -284,7 +284,7 @@ public class AppSocketServer {
|
|||
msg.set("xPipeMessage", json);
|
||||
|
||||
var writer = new StringWriter();
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
var mapper = JacksonMapper.getDefault();
|
||||
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
|
||||
g.writeTree(msg);
|
||||
} catch (IOException ex) {
|
||||
|
@ -300,14 +300,14 @@ public class AppSocketServer {
|
|||
|
||||
public void sendClientErrorResponse(Socket outSocket, String message) throws Exception {
|
||||
var err = new ClientErrorMessage(message);
|
||||
ObjectNode json = JacksonMapper.newMapper().valueToTree(err);
|
||||
ObjectNode json = JacksonMapper.getDefault().valueToTree(err);
|
||||
var msg = JsonNodeFactory.instance.objectNode();
|
||||
msg.set("xPipeClientError", json);
|
||||
|
||||
// Don't log this as it clutters the output
|
||||
// TrackEvent.trace("beacon", "Sending raw client error:\n" + json.toPrettyString());
|
||||
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
var mapper = JacksonMapper.getDefault();
|
||||
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
|
||||
var gen = mapper.createGenerator(blockOut);
|
||||
gen.writeTree(msg);
|
||||
|
@ -316,14 +316,14 @@ public class AppSocketServer {
|
|||
|
||||
public void sendServerErrorResponse(Socket outSocket, Throwable ex) throws Exception {
|
||||
var err = new ServerErrorMessage(UUID.randomUUID(), ex);
|
||||
ObjectNode json = JacksonMapper.newMapper().valueToTree(err);
|
||||
ObjectNode json = JacksonMapper.getDefault().valueToTree(err);
|
||||
var msg = JsonNodeFactory.instance.objectNode();
|
||||
msg.set("xPipeServerError", json);
|
||||
|
||||
// Don't log this as it clutters the output
|
||||
// TrackEvent.trace("beacon", "Sending raw server error:\n" + json.toPrettyString());
|
||||
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
var mapper = JacksonMapper.getDefault();
|
||||
try (OutputStream blockOut = BeaconFormat.writeBlocks(outSocket.getOutputStream())) {
|
||||
var gen = mapper.createGenerator(blockOut);
|
||||
gen.writeTree(msg);
|
||||
|
@ -347,7 +347,7 @@ public class AppSocketServer {
|
|||
throw new IllegalArgumentException("Unknown request id: " + type);
|
||||
}
|
||||
|
||||
var reader = JacksonMapper.newMapper().readerFor(prov.get().getRequestClass());
|
||||
var reader = JacksonMapper.getDefault().readerFor(prov.get().getRequestClass());
|
||||
return reader.readValue(content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import javafx.animation.KeyFrame;
|
|||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Application;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
@ -84,12 +83,6 @@ public class AppTheme {
|
|||
AppPrefs.get().theme.addListener((c, o, n) -> {
|
||||
changeTheme(n);
|
||||
});
|
||||
|
||||
Window.getWindows().addListener((ListChangeListener<? super Window>) c -> {
|
||||
c.getList().forEach(window -> {
|
||||
window.opacityProperty().bind(AppPrefs.get().windowOpacity());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void setDefault(boolean dark) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import io.xpipe.app.storage.DataStorage;
|
|||
import io.xpipe.app.util.FeatureProvider;
|
||||
import io.xpipe.app.util.FileBridge;
|
||||
import io.xpipe.app.util.LockedSecretValue;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
||||
public class BaseMode extends OperationMode {
|
||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.app.issue.*;
|
|||
import io.xpipe.app.launcher.LauncherCommand;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.XPipeSession;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.FailableRunnable;
|
||||
import io.xpipe.core.util.XPipeDaemonMode;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package io.xpipe.app.exchange;
|
||||
|
||||
import io.xpipe.app.ext.DataSourceProvider;
|
||||
import io.xpipe.app.ext.DataSourceProviders;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
|
@ -9,49 +7,11 @@ import io.xpipe.beacon.ClientException;
|
|||
import io.xpipe.beacon.RequestMessage;
|
||||
import io.xpipe.beacon.ResponseMessage;
|
||||
import io.xpipe.beacon.exchange.MessageExchange;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.impl.NamedStore;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.DataStoreId;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends ResponseMessage> extends MessageExchange {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default <T extends DataSource<?>> Dialog toCompleteConfig(
|
||||
@NonNull DataSource<?> source, @NonNull DataSourceProvider<T> p, boolean all) throws ClientException {
|
||||
var dialog = p.configDialog((T) source, all);
|
||||
if (dialog == null) {
|
||||
throw new ClientException(
|
||||
String.format("Data source of type %s does not support editing from the CLI", p.getId()));
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
default DataSourceProvider<?> getProvider(@NonNull String id) throws ClientException {
|
||||
Optional<DataSourceProvider<?>> prov = DataSourceProviders.byName(id);
|
||||
if (prov.isEmpty()) {
|
||||
throw new ClientException("No matching data source type found for type " + id);
|
||||
}
|
||||
return prov.get();
|
||||
}
|
||||
|
||||
default DataStore resolveStore(@NonNull DataStore in, boolean acceptDisabled) throws ClientException {
|
||||
try {
|
||||
if (in instanceof NamedStore n) {
|
||||
var found = DataStorage.get().getStoreEntry(n.getName(), acceptDisabled);
|
||||
return found.getStore();
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new ClientException(ex.getMessage());
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
default DataStoreEntry getStoreEntryByName(@NonNull String name, boolean acceptDisabled) throws ClientException {
|
||||
var store = DataStorage.get().getStoreEntryIfPresent(name);
|
||||
if (store.isEmpty()) {
|
||||
|
@ -73,7 +33,7 @@ public interface MessageExchangeImpl<RQ extends RequestMessage, RS extends Respo
|
|||
throw new ClientException(
|
||||
String.format("Store %s is disabled", store.get().getName()));
|
||||
}
|
||||
if (!store.get().getState().isUsable() && !acceptUnusable) {
|
||||
if (!store.get().getValidity().isUsable() && !acceptUnusable) {
|
||||
throw new ClientException(
|
||||
String.format("Store %s is not completely configured", store.get().getName()));
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package io.xpipe.app.exchange;
|
||||
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.exchange.ProxyFunctionExchange;
|
||||
|
||||
public class ProxyFunctionExchangeImpl extends ProxyFunctionExchange
|
||||
implements MessageExchangeImpl<ProxyFunctionExchange.Request, ProxyFunctionExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
msg.getFunction().callLocal();
|
||||
return ProxyFunctionExchange.Response.builder()
|
||||
.function(msg.getFunction())
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package io.xpipe.app.exchange;
|
||||
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.exchange.ProxyReadConnectionExchange;
|
||||
import io.xpipe.core.impl.OutputStreamStore;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceReadConnection;
|
||||
import io.xpipe.core.source.WriteMode;
|
||||
|
||||
public class ProxyReadConnectionExchangeImpl extends ProxyReadConnectionExchange
|
||||
implements MessageExchangeImpl<ProxyReadConnectionExchange.Request, ProxyReadConnectionExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
handler.postResponse(() -> {
|
||||
var outputSource = DataSource.createInternalDataSource(
|
||||
msg.getSource().getType(), new OutputStreamStore(handler.sendBody()));
|
||||
try (DataSourceReadConnection r = msg.getSource().openReadConnection();
|
||||
var w = outputSource.openWriteConnection(WriteMode.REPLACE)) {
|
||||
r.init();
|
||||
w.init();
|
||||
r.forward(w);
|
||||
}
|
||||
});
|
||||
return ProxyReadConnectionExchange.Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package io.xpipe.app.exchange;
|
||||
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.exchange.ProxyWriteConnectionExchange;
|
||||
import io.xpipe.core.impl.InputStreamStore;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceReadConnection;
|
||||
|
||||
public class ProxyWriteConnectionExchangeImpl extends ProxyWriteConnectionExchange
|
||||
implements MessageExchangeImpl<ProxyWriteConnectionExchange.Request, ProxyWriteConnectionExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var outputSource = msg.getSource();
|
||||
var inputSource = DataSource.createInternalDataSource(
|
||||
outputSource.getType(), new InputStreamStore(handler.receiveBody()));
|
||||
try (DataSourceReadConnection r = inputSource.openReadConnection();
|
||||
var w = outputSource.openWriteConnection(msg.getMode())) {
|
||||
r.init();
|
||||
w.init();
|
||||
r.forward(w);
|
||||
}
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -10,12 +10,10 @@ public class QueryStoreExchangeImpl extends QueryStoreExchange
|
|||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var store = getStoreEntryByName(msg.getName(), true);
|
||||
var information = store.getInformation();
|
||||
var summary = store.getProvider().toSummaryString(store.getStore(), 100);
|
||||
var summary = "";
|
||||
var dialog = store.getProvider().dialogForStore(store.getStore().asNeeded());
|
||||
var config = new DialogMapper(dialog).handle();
|
||||
return Response.builder()
|
||||
.information(information)
|
||||
.summary(summary)
|
||||
.internalStore(store.getStore())
|
||||
.provider(store.getProvider().getId())
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
package io.xpipe.app.exchange;
|
||||
|
||||
import io.xpipe.app.ext.DataSourceProvider;
|
||||
import io.xpipe.app.ext.DataSourceProviders;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
import io.xpipe.beacon.exchange.ReadExchange;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
|
||||
public class ReadExchangeImpl extends ReadExchange
|
||||
implements MessageExchangeImpl<ReadExchange.Request, ReadExchange.Response> {
|
||||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) throws Exception {
|
||||
var store = resolveStore(msg.getStore(), false);
|
||||
DataSourceProvider<?> provider;
|
||||
if (msg.getProvider() == null) {
|
||||
provider = DataSourceProviders.byPreferredStore(store, null).orElse(null);
|
||||
} else {
|
||||
provider = getProvider(msg.getProvider());
|
||||
}
|
||||
|
||||
var typeQ = Dialog.skipIf(
|
||||
Dialog.retryIf(
|
||||
Dialog.query(
|
||||
"Data source type could not be determined.\nSpecify type explicitly",
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
QueryConverter.STRING),
|
||||
(String type) -> {
|
||||
return DataSourceProviders.byName(type).isEmpty() ? "Unknown type: " + type : null;
|
||||
}),
|
||||
() -> provider != null);
|
||||
|
||||
var config = Dialog.lazy(() -> {
|
||||
if (!provider.couldSupportStore(store)) {
|
||||
throw new ClientException("Type " + provider.getId() + " does not support store");
|
||||
}
|
||||
|
||||
var defaultDesc = provider.createDefaultSource(store);
|
||||
return toCompleteConfig(defaultDesc, provider, msg.isConfigureAll());
|
||||
});
|
||||
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import io.xpipe.app.exchange.MessageExchangeImpl;
|
|||
import io.xpipe.app.update.XPipeInstanceHelper;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.exchange.cli.InstanceExchange;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
|
||||
public class InstanceExchangeImpl extends InstanceExchange
|
||||
implements MessageExchangeImpl<InstanceExchange.Request, InstanceExchange.Response> {
|
||||
|
|
|
@ -19,7 +19,6 @@ public class ListStoresExchangeImpl extends ListStoresExchange
|
|||
.map(col -> StoreListEntry.builder()
|
||||
.id(DataStorage.get().getId(col))
|
||||
.type(col.getProvider().getId())
|
||||
.information(col.getInformation())
|
||||
.build())
|
||||
.sorted(Comparator.comparing(en -> en.getId().toString()))
|
||||
.toList();
|
||||
|
|
|
@ -11,7 +11,7 @@ public class RenameStoreExchangeImpl extends RenameStoreExchange
|
|||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
var s = DataStorage.get().getStoreEntry(msg.getStoreName(), true);
|
||||
DataStorage.get().renameStoreEntry(s, msg.getNewName());
|
||||
s.setName(msg.getNewName());
|
||||
return Response.builder().build();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.exchange.DialogExchangeImpl;
|
|||
import io.xpipe.app.exchange.MessageExchangeImpl;
|
||||
import io.xpipe.app.ext.DataStoreProvider;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.issue.ExceptionConverter;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.beacon.BeaconHandler;
|
||||
import io.xpipe.beacon.ClientException;
|
||||
|
@ -51,7 +50,7 @@ public class StoreAddExchangeImpl extends StoreAddExchange
|
|||
return;
|
||||
}
|
||||
|
||||
DataStorage.get().addStoreEntry(name.getValue(), store);
|
||||
DataStorage.get().addStoreIfNotPresent(name.getValue(), store);
|
||||
});
|
||||
|
||||
return StoreAddExchange.Response.builder().config(config).build();
|
||||
|
@ -64,12 +63,6 @@ public class StoreAddExchangeImpl extends StoreAddExchange
|
|||
return "Store is null";
|
||||
}
|
||||
|
||||
try {
|
||||
store.validate();
|
||||
} catch (Exception ex) {
|
||||
return ExceptionConverter.convertMessage(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.map((String msg) -> {
|
||||
|
@ -95,7 +88,6 @@ public class StoreAddExchangeImpl extends StoreAddExchange
|
|||
DataStore s = creator.getResult();
|
||||
String d = "";
|
||||
try {
|
||||
d = provider.queryInformationString(s, 50);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
if (d != null) {
|
||||
|
|
|
@ -15,23 +15,23 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
|
|||
|
||||
@Override
|
||||
public Response handleRequest(BeaconHandler handler, Request msg) {
|
||||
var categories = DataStoreProvider.DisplayCategory.values();
|
||||
var categories = DataStoreProvider.CreationCategory.values();
|
||||
var all = DataStoreProviders.getAll();
|
||||
var map = Arrays.stream(categories)
|
||||
.collect(Collectors.toMap(category -> getName(category), category -> all.stream()
|
||||
.filter(dataStoreProvider ->
|
||||
dataStoreProvider.getDisplayCategory().equals(category))
|
||||
category.equals(dataStoreProvider.getCreationCategory()))
|
||||
.map(p -> ProviderEntry.builder()
|
||||
.id(p.getId())
|
||||
.description(p.getDisplayDescription())
|
||||
.hidden(!p.canManuallyCreate())
|
||||
.hidden(p.getCreationCategory() == null)
|
||||
.build())
|
||||
.toList()));
|
||||
|
||||
return Response.builder().entries(map).build();
|
||||
}
|
||||
|
||||
private String getName(DataStoreProvider.DisplayCategory category) {
|
||||
private String getName(DataStoreProvider.CreationCategory category) {
|
||||
return category.name().substring(0, 1).toUpperCase()
|
||||
+ category.name().substring(1).toLowerCase();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
@ -91,10 +90,6 @@ public interface ActionProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
default DataSourceCallSite<?> getDataSourceCallSite() {
|
||||
return null;
|
||||
}
|
||||
|
||||
interface DefaultDataStoreCallSite<T extends DataStore> {
|
||||
|
||||
Action createAction(T store);
|
||||
|
@ -142,27 +137,4 @@ public interface ActionProvider {
|
|||
return ActiveType.ONLY_SHOW_IF_ENABLED;
|
||||
}
|
||||
}
|
||||
|
||||
interface DataSourceCallSite<T extends DataSource<?>> {
|
||||
|
||||
Action createAction(T source);
|
||||
|
||||
Class<T> getApplicableClass();
|
||||
|
||||
default boolean isMajor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean isApplicable(T o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ObservableValue<String> getName(T source);
|
||||
|
||||
String getIcon(T source);
|
||||
|
||||
default boolean showIfDisabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.source.CollectionDataSource;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataSourceType;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface DataSourceProvider<T extends DataSource<?>> {
|
||||
|
||||
default CollectionDataSource<?> createContainer(T source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default void validate() {
|
||||
getCategory();
|
||||
getSourceClass();
|
||||
}
|
||||
|
||||
default Category getCategory() {
|
||||
if (getFileProvider() != null) {
|
||||
return Category.STREAM;
|
||||
}
|
||||
|
||||
throw new ExtensionException("Provider has no set general type");
|
||||
}
|
||||
|
||||
default boolean supportsConversion(T in, DataSourceType t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default DataSource<?> convert(T in, DataSourceType t) throws Exception {
|
||||
throw new ExtensionException();
|
||||
}
|
||||
|
||||
default void init() throws Exception {}
|
||||
|
||||
default String i18n(String key) {
|
||||
return AppI18n.get(i18nKey(key));
|
||||
}
|
||||
|
||||
default String i18nKey(String key) {
|
||||
return getId() + "." + key;
|
||||
}
|
||||
|
||||
default Region configGui(Property<T> source, boolean preferQuiet) throws Exception {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getDisplayName() {
|
||||
return i18n("displayName");
|
||||
}
|
||||
|
||||
default String getDisplayDescription() {
|
||||
return i18n("displayDescription");
|
||||
}
|
||||
|
||||
default String getModuleName() {
|
||||
var n = getClass().getModule().getName();
|
||||
var i = n.lastIndexOf('.');
|
||||
return i != -1 ? n.substring(i + 1) : n;
|
||||
}
|
||||
|
||||
default String queryInformationString(DataStore store, int length) {
|
||||
return getDisplayName();
|
||||
}
|
||||
|
||||
default String getDisplayIconFileName() {
|
||||
return getModuleName() + ":" + getId() + "_icon.png";
|
||||
}
|
||||
|
||||
Dialog configDialog(T source, boolean all);
|
||||
|
||||
default boolean shouldShow(DataSourceType type) {
|
||||
return type == null || type == getPrimaryType();
|
||||
}
|
||||
|
||||
DataSourceType getPrimaryType();
|
||||
|
||||
/**
|
||||
* Checks whether this provider prefers a certain kind of store.
|
||||
* This is important for the correct autodetection of a store.
|
||||
*/
|
||||
boolean prefersStore(DataStore store, DataSourceType type);
|
||||
|
||||
/**
|
||||
* Checks whether this provider supports the store in principle.
|
||||
* This method should not perform any further checks,
|
||||
* just check whether it may be possible that the store is supported.
|
||||
* <p>
|
||||
* This method will be called for validation purposes.
|
||||
*/
|
||||
boolean couldSupportStore(DataStore store);
|
||||
|
||||
/**
|
||||
* Performs a deep inspection to check whether this provider supports a given store.
|
||||
* <p>
|
||||
* This functionality will be used in case no preferred provider has been found.
|
||||
*/
|
||||
default boolean supportsStore(DataStore store) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default FileProvider getFileProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String getId() {
|
||||
return getPossibleNames().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create a useful data source descriptor from a data store.
|
||||
* The result does not need to be always right, it should only reflect the best effort.
|
||||
*/
|
||||
T createDefaultSource(DataStore input) throws Exception;
|
||||
|
||||
Class<T> getSourceClass();
|
||||
|
||||
List<String> getPossibleNames();
|
||||
|
||||
enum Category {
|
||||
STREAM,
|
||||
DATABASE
|
||||
}
|
||||
|
||||
interface FileProvider {
|
||||
|
||||
String getFileName();
|
||||
|
||||
Map<String, List<String>> getFileExtensions();
|
||||
}
|
||||
}
|
|
@ -1,175 +0,0 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.impl.FileStore;
|
||||
import io.xpipe.core.source.*;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DataSourceProviders {
|
||||
|
||||
private static List<DataSourceProvider<?>> ALL;
|
||||
|
||||
public static void init(ModuleLayer layer) {
|
||||
if (ALL == null) {
|
||||
ALL = ServiceLoader.load(layer, DataSourceProvider.class).stream()
|
||||
.map(p -> (DataSourceProvider<?>) p.get())
|
||||
.sorted(Comparator.comparing(DataSourceProvider::getId))
|
||||
.collect(Collectors.toList());
|
||||
ALL.removeIf(p -> {
|
||||
try {
|
||||
p.init();
|
||||
p.validate();
|
||||
return false;
|
||||
} catch (Throwable e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static DataSourceProvider<?> getInternalProviderForType(DataSourceType t) {
|
||||
try {
|
||||
return switch (t) {
|
||||
case TABLE -> DataSourceProviders.byId("xpbt");
|
||||
case STRUCTURE -> DataSourceProviders.byId("xpbs");
|
||||
case TEXT -> DataSourceProviders.byId("text");
|
||||
case RAW -> DataSourceProviders.byId("binary");
|
||||
// TODO
|
||||
case COLLECTION -> null;
|
||||
};
|
||||
} catch (Exception ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public static StructureDataSource<FileStore> createLocalStructureDescriptor(DataStore store) {
|
||||
return (StructureDataSource<FileStore>) DataSourceProviders.byId("xpbs")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0]
|
||||
.newInstance(store);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public static RawDataSource<FileStore> createLocalRawDescriptor(DataStore store) {
|
||||
return (RawDataSource<FileStore>) DataSourceProviders.byId("binary")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0]
|
||||
.newInstance(store);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public static RawDataSource<FileStore> createLocalCollectionDescriptor(DataStore store) {
|
||||
return (RawDataSource<FileStore>) DataSourceProviders.byId("br")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0]
|
||||
.newInstance(store);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public static TextDataSource<FileStore> createLocalTextDescriptor(DataStore store) {
|
||||
return (TextDataSource<FileStore>) DataSourceProviders.byId("text")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0]
|
||||
.newInstance(store);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public static TableDataSource<FileStore> createLocalTableDescriptor(DataStore store) {
|
||||
return (TableDataSource<FileStore>) DataSourceProviders.byId("xpbt")
|
||||
.getSourceClass()
|
||||
.getDeclaredConstructors()[0]
|
||||
.newInstance(store);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends DataSourceProvider<?>> T byId(String name) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return (T) ALL.stream()
|
||||
.filter(d -> d.getId().equals(name))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Provider " + name + " not found"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <C extends DataSource<?>, T extends DataSourceProvider<C>> T byDataSourceClass(Class<C> c) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return (T) ALL.stream()
|
||||
.filter(d -> d.getSourceClass().equals(c))
|
||||
.findAny()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Provider for " + c.getSimpleName() + " not found"));
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider<?>> byName(String name) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream()
|
||||
.filter(d -> d.getPossibleNames().stream()
|
||||
.anyMatch(s -> nameAlternatives(s).stream().anyMatch(s1 -> s1.equalsIgnoreCase(name)))
|
||||
|| d.getId().equalsIgnoreCase(name))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
private static List<String> nameAlternatives(String name) {
|
||||
var split = List.of(name.split("_"));
|
||||
return List.of(
|
||||
String.join(" ", split),
|
||||
String.join("_", split),
|
||||
String.join("-", split),
|
||||
split.stream()
|
||||
.map(s -> s.equals(split.get(0)) ? s : s.substring(0, 1).toUpperCase() + s.substring(1))
|
||||
.collect(Collectors.joining()));
|
||||
}
|
||||
|
||||
public static DataSource<?> createDefault(DataStore store) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
var preferred = DataSourceProviders.byPreferredStore(store, null);
|
||||
try {
|
||||
return preferred.isPresent()
|
||||
? preferred.get().createDefaultSource(store).asNeeded()
|
||||
: null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<DataSourceProvider<?>> byPreferredStore(DataStore store, DataSourceType type) {
|
||||
if (ALL == null) {
|
||||
throw new IllegalStateException("Not initialized");
|
||||
}
|
||||
|
||||
return ALL.stream()
|
||||
.filter(d -> type == null || d.getPrimaryType() == type)
|
||||
.filter(d -> d.getFileProvider() != null)
|
||||
.filter(d -> d.prefersStore(store, type))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
public static List<DataSourceProvider<?>> getAll() {
|
||||
return ALL;
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.util.Validator;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public interface DataSourceTarget {
|
||||
|
||||
List<DataSourceTarget> ALL = new ArrayList<>();
|
||||
|
||||
class Loader implements ModuleLayerLoader {
|
||||
|
||||
@Override
|
||||
public void init(ModuleLayer layer) {
|
||||
ALL.clear();
|
||||
ALL.addAll(ServiceLoader.load(layer, DataSourceTarget.class).stream()
|
||||
.map(ServiceLoader.Provider::get)
|
||||
.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresFullDaemon() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prioritizeLoading() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Optional<DataSourceTarget> byId(String id) {
|
||||
return ALL.stream().filter(d -> d.getId().equals(id)).findAny();
|
||||
}
|
||||
|
||||
static List<DataSourceTarget> getAll() {
|
||||
return ALL;
|
||||
}
|
||||
|
||||
default InstructionsDisplay createRetrievalInstructions(DataSource<?> source, ObservableValue<DataStoreId> id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default InstructionsDisplay createUpdateInstructions(DataSource<?> source, ObservableValue<DataStoreId> id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String getId();
|
||||
|
||||
ObservableValue<String> getName();
|
||||
|
||||
Category getCategory();
|
||||
|
||||
AccessType getAccessType();
|
||||
|
||||
String getSetupGuideURL();
|
||||
|
||||
default String getGraphicIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default boolean isApplicable(DataSource<?> source) {
|
||||
return true;
|
||||
}
|
||||
|
||||
enum Category {
|
||||
PROGRAMMING_LANGUAGE,
|
||||
APPLICATION,
|
||||
OTHER
|
||||
}
|
||||
|
||||
enum AccessType {
|
||||
ACTIVE,
|
||||
PASSIVE
|
||||
}
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
class InstructionsDisplay {
|
||||
Region region;
|
||||
Runnable onFinish;
|
||||
Validator validator;
|
||||
|
||||
public InstructionsDisplay(Region region) {
|
||||
this.region = region;
|
||||
onFinish = null;
|
||||
validator = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,21 @@
|
|||
package io.xpipe.app.ext;
|
||||
|
||||
import io.xpipe.app.comp.base.MarkdownComp;
|
||||
import io.xpipe.app.comp.base.SystemStateComp;
|
||||
import io.xpipe.app.comp.storage.store.*;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryComp;
|
||||
import io.xpipe.app.comp.storage.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.comp.storage.store.StoreSection;
|
||||
import io.xpipe.app.comp.storage.store.StoreSectionComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.core.dialog.Dialog;
|
||||
import io.xpipe.core.store.*;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.JacksonizedValue;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -36,8 +39,6 @@ public interface DataStoreProvider {
|
|||
}
|
||||
}
|
||||
|
||||
default void preAdd(DataStore store) {}
|
||||
|
||||
default String browserDisplayName(DataStore store) {
|
||||
var e = DataStorage.get().getStoreDisplayName(store);
|
||||
return e.orElse("?");
|
||||
|
@ -64,16 +65,7 @@ public interface DataStoreProvider {
|
|||
}
|
||||
|
||||
default Comp<?> stateDisplay(StoreEntryWrapper w) {
|
||||
var state = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
return w.getState().getValue() == DataStoreEntry.State.COMPLETE_BUT_INVALID
|
||||
? SystemStateComp.State.FAILURE
|
||||
: w.getState().getValue() == DataStoreEntry.State.COMPLETE_AND_VALID
|
||||
? SystemStateComp.State.SUCCESS
|
||||
: SystemStateComp.State.OTHER;
|
||||
},
|
||||
w.getState());
|
||||
return new SystemStateComp(state);
|
||||
return Comp.empty();
|
||||
}
|
||||
|
||||
default Comp<?> createInsightsComp(ObservableValue<DataStore> store) {
|
||||
|
@ -97,19 +89,15 @@ public interface DataStoreProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
default DisplayCategory getDisplayCategory() {
|
||||
return DisplayCategory.OTHER;
|
||||
}
|
||||
|
||||
default DataStore getLogicalParent(DataStore store) {
|
||||
default CreationCategory getCreationCategory() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default DataStore getDisplayParent(DataStore store) {
|
||||
return getLogicalParent(store);
|
||||
default DataStoreEntry getDisplayParent(DataStoreEntry store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default GuiDialog guiDialog(Property<DataStore> store) {
|
||||
default GuiDialog guiDialog(DataStoreEntry entry, Property<DataStore> store) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -126,16 +114,12 @@ public interface DataStoreProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
default String queryInformationString(DataStore store, int length) throws Exception {
|
||||
default String summaryString(StoreEntryWrapper wrapper) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default String queryInvalidInformationString(DataStore store, int length) {
|
||||
return "Connection failed";
|
||||
}
|
||||
|
||||
default String toSummaryString(DataStore store, int length) {
|
||||
return null;
|
||||
default ObservableValue<String> informationString(StoreEntryWrapper wrapper) {
|
||||
return new SimpleStringProperty(null);
|
||||
}
|
||||
|
||||
default String i18n(String key) {
|
||||
|
@ -173,10 +157,6 @@ public interface DataStoreProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
default boolean requiresFrequentRefresh() {
|
||||
return getStoreClasses().stream().anyMatch(aClass -> FixedHierarchyStore.class.isAssignableFrom(aClass));
|
||||
}
|
||||
|
||||
default DataStore defaultStore() {
|
||||
return null;
|
||||
}
|
||||
|
@ -189,22 +169,12 @@ public interface DataStoreProvider {
|
|||
|
||||
List<Class<?>> getStoreClasses();
|
||||
|
||||
default boolean canManuallyCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
enum DataCategory {
|
||||
STREAM,
|
||||
SHELL,
|
||||
DATABASE
|
||||
}
|
||||
|
||||
enum DisplayCategory {
|
||||
enum CreationCategory {
|
||||
HOST,
|
||||
DATABASE,
|
||||
SHELL,
|
||||
COMMAND,
|
||||
TUNNEL,
|
||||
OTHER
|
||||
SCRIPT
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,6 @@ public class XPipeServiceProviders {
|
|||
ProcessControlProvider.init(layer);
|
||||
|
||||
TrackEvent.info("Loading extension providers ...");
|
||||
DataSourceProviders.init(layer);
|
||||
for (DataSourceProvider<?> p : DataSourceProviders.getAll()) {
|
||||
TrackEvent.trace("Loaded data source provider " + p.getId());
|
||||
JacksonMapper.configure(objectMapper -> {
|
||||
objectMapper.registerSubtypes(new NamedType(p.getSourceClass()));
|
||||
});
|
||||
}
|
||||
|
||||
DataStoreProviders.init(layer);
|
||||
for (DataStoreProvider p : DataStoreProviders.getAll()) {
|
||||
TrackEvent.trace("Loaded data store provider " + p.getId());
|
||||
|
|
|
@ -44,5 +44,10 @@ public class ContextMenuAugment<S extends CompStructure<?>> implements Augment<S
|
|||
event.consume();
|
||||
}
|
||||
});
|
||||
r.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if (show.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import io.xpipe.app.ext.DataStoreProviders;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
|
@ -37,47 +39,51 @@ import java.util.function.Predicate;
|
|||
public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
||||
|
||||
public static <T extends DataStore> DataStoreChoiceComp<T> other(
|
||||
Property<T> selected, Class<T> clazz, Predicate<T> filter) {
|
||||
Property<DataStoreEntryRef<T>> selected, Class<T> clazz, Predicate<DataStoreEntryRef<T>> filter) {
|
||||
return new DataStoreChoiceComp<>(Mode.OTHER, null, selected, clazz, filter);
|
||||
}
|
||||
|
||||
public static DataStoreChoiceComp<ShellStore> proxy(Property<ShellStore> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, shellStore -> true);
|
||||
public static DataStoreChoiceComp<ShellStore> proxy(Property<DataStoreEntryRef<ShellStore>> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.PROXY, null, selected, ShellStore.class, null);
|
||||
}
|
||||
|
||||
public static DataStoreChoiceComp<ShellStore> host(Property<ShellStore> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, shellStore -> true);
|
||||
public static DataStoreChoiceComp<ShellStore> host(Property<DataStoreEntryRef<ShellStore>> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.HOST, null, selected, ShellStore.class, null);
|
||||
}
|
||||
|
||||
public static DataStoreChoiceComp<ShellStore> environment(ShellStore self, Property<ShellStore> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.ENVIRONMENT, self, selected, ShellStore.class, shellStore -> true);
|
||||
public static DataStoreChoiceComp<ShellStore> environment(
|
||||
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.HOST, self, selected, ShellStore.class, shellStoreDataStoreEntryRef -> shellStoreDataStoreEntryRef.get().getProvider().canHaveSubShells());
|
||||
}
|
||||
|
||||
public static DataStoreChoiceComp<ShellStore> proxy(ShellStore self, Property<ShellStore> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.PROXY, self, selected, ShellStore.class, shellStore -> true);
|
||||
public static DataStoreChoiceComp<ShellStore> proxy(
|
||||
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.PROXY, self, selected, ShellStore.class, null);
|
||||
}
|
||||
|
||||
public static DataStoreChoiceComp<ShellStore> host(ShellStore self, Property<ShellStore> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.HOST, self, selected, ShellStore.class, shellStore -> true);
|
||||
public static DataStoreChoiceComp<ShellStore> host(
|
||||
DataStoreEntry self, Property<DataStoreEntryRef<ShellStore>> selected) {
|
||||
return new DataStoreChoiceComp<>(Mode.HOST, self, selected, ShellStore.class, null);
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
HOST,
|
||||
ENVIRONMENT,
|
||||
OTHER,
|
||||
PROXY
|
||||
}
|
||||
|
||||
private final Mode mode;
|
||||
private final T self;
|
||||
private final Property<T> selected;
|
||||
private final DataStoreEntry self;
|
||||
private final Property<DataStoreEntryRef<T>> selected;
|
||||
private final Class<T> storeClass;
|
||||
private final Predicate<T> applicableCheck;
|
||||
private final Predicate<DataStoreEntryRef<T>> applicableCheck;
|
||||
|
||||
private Popover popover;
|
||||
|
||||
private Popover getPopover() {
|
||||
if (popover == null) {
|
||||
// Rebuild popover if we have a non-null condition to allow for the content to be updated in case the condition
|
||||
// changed
|
||||
if (popover == null || applicableCheck != null) {
|
||||
var selectedCategory = new SimpleObjectProperty<>(
|
||||
StoreViewState.get().getActiveCategory().getValue());
|
||||
var filterText = new SimpleStringProperty();
|
||||
|
@ -85,14 +91,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
Predicate<StoreEntryWrapper> applicable = storeEntryWrapper -> {
|
||||
var e = storeEntryWrapper.getEntry();
|
||||
|
||||
if (e.getStore() == self) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var store = e.getStore();
|
||||
if (!(mode == Mode.ENVIRONMENT)
|
||||
&& e.getProvider() != null
|
||||
&& !e.getProvider().canHaveSubShells()) {
|
||||
if (e.equals(self)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -100,15 +99,18 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
if (e.getStore() == null) {
|
||||
return false;
|
||||
}
|
||||
return storeClass.isAssignableFrom(e.getStore().getClass()) && e.getState().isUsable() && applicableCheck.test(e.getStore().asNeeded());
|
||||
|
||||
return storeClass.isAssignableFrom(e.getStore().getClass())
|
||||
&& e.getValidity().isUsable()
|
||||
&& (applicableCheck == null
|
||||
|| applicableCheck.test(e.ref()));
|
||||
};
|
||||
var section = StoreSectionMiniComp.createList(
|
||||
StoreSection.createTopLevel(
|
||||
StoreViewState.get().getAllEntries(), applicable, filterText, selectedCategory),
|
||||
(s, comp) -> {
|
||||
comp.apply(struc -> struc.get().setOnAction(event -> {
|
||||
selected.setValue(
|
||||
s.getWrapper().getEntry().getStore().asNeeded());
|
||||
selected.setValue(s.getWrapper().getEntry().ref());
|
||||
popover.hide();
|
||||
event.consume();
|
||||
}));
|
||||
|
@ -137,7 +139,10 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
MenuButton m = new MenuButton(null, new FontIcon("mdi2p-plus-box-outline"));
|
||||
StoreCreationMenu.addButtons(m);
|
||||
return m;
|
||||
}).padding(new Insets(-2)).styleClass(Styles.RIGHT_PILL).grow(false, true);
|
||||
})
|
||||
.padding(new Insets(-2))
|
||||
.styleClass(Styles.RIGHT_PILL)
|
||||
.grow(false, true);
|
||||
|
||||
var top = new HorizontalComp(List.of(category, filter.hgrow(), addButton))
|
||||
.styleClass("top")
|
||||
|
@ -182,16 +187,18 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
return new Label(name, imgView);
|
||||
}
|
||||
|
||||
private String toName(DataStore store) {
|
||||
if (store == null) {
|
||||
private String toName(DataStoreEntry entry) {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mode == Mode.PROXY && store instanceof ShellStore && ShellStore.isLocal(store.asNeeded())) {
|
||||
if (mode == Mode.PROXY
|
||||
&& entry.getStore() instanceof ShellStore
|
||||
&& ShellStore.isLocal(entry.getStore().asNeeded())) {
|
||||
return AppI18n.get("none");
|
||||
}
|
||||
|
||||
return DataStorage.get().getStoreDisplayName(store).orElse("?");
|
||||
return entry.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -199,7 +206,7 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
var button = new ButtonComp(
|
||||
Bindings.createStringBinding(
|
||||
() -> {
|
||||
return toName(selected.getValue());
|
||||
return selected.getValue() != null ? toName(selected.getValue().getEntry()) : null;
|
||||
},
|
||||
selected),
|
||||
() -> {});
|
||||
|
@ -207,17 +214,18 @@ public class DataStoreChoiceComp<T extends DataStore> extends SimpleComp {
|
|||
struc.get().setMaxWidth(2000);
|
||||
struc.get().setAlignment(Pos.CENTER_LEFT);
|
||||
struc.get()
|
||||
.setGraphic(PrettyImageHelper.ofSvg(Bindings.createStringBinding(
|
||||
.setGraphic(PrettyImageHelper.ofSvg(
|
||||
Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (selected.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DataStorage.get()
|
||||
.getStoreEntryIfPresent(selected.getValue())
|
||||
.map(entry -> entry.getProvider()
|
||||
.getDisplayIconFileName(selected.getValue()))
|
||||
.orElse(null);
|
||||
return selected.getValue()
|
||||
.get()
|
||||
.getProvider()
|
||||
.getDisplayIconFileName(selected.getValue()
|
||||
.getStore());
|
||||
},
|
||||
selected),
|
||||
16,
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class DataStoreListChoiceComp<T extends DataStore> extends SimpleComp {
|
||||
|
||||
private final ListProperty<DataStoreEntryRef<T>> selectedList;
|
||||
private final Class<T> storeClass;
|
||||
private final Predicate<DataStoreEntryRef<T>> applicableCheck;
|
||||
|
||||
public DataStoreListChoiceComp(ListProperty<DataStoreEntryRef<T>> selectedList, Class<T> storeClass, Predicate<DataStoreEntryRef<T>> applicableCheck) {
|
||||
this.selectedList = selectedList;
|
||||
this.storeClass = storeClass;
|
||||
this.applicableCheck = applicableCheck;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var list = new ListBoxViewComp<>(selectedList, selectedList, t -> {
|
||||
if (t == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var label = new LabelComp(t.get().getName()).apply(struc -> struc.get().setGraphic(PrettyImageHelper.ofFixedSmallSquare(
|
||||
t.get().getProvider().getDisplayIconFileName(t.getStore())).createRegion()));
|
||||
var delete = new IconButtonComp("mdal-delete_outline", () -> {
|
||||
selectedList.remove(t);
|
||||
});
|
||||
var hbox = new HorizontalComp(List.of(label, Comp.hspacer(), delete)).styleClass("entry");
|
||||
return hbox;
|
||||
}).padding(new Insets(0)).apply(struc -> struc.get().setMinHeight(0)).apply(struc -> ((VBox) struc.get().getContent()).setSpacing(5));
|
||||
var selected = new SimpleObjectProperty<DataStoreEntryRef<T>>();
|
||||
var add = new DataStoreChoiceComp<T>(DataStoreChoiceComp.Mode.OTHER, null, selected, storeClass, applicableCheck);
|
||||
selected.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
if (!selectedList.contains(newValue)
|
||||
&& (applicableCheck == null
|
||||
|| applicableCheck.test(newValue))) {
|
||||
selectedList.add(newValue);
|
||||
}
|
||||
selected.setValue(null);
|
||||
}
|
||||
});
|
||||
var vbox = new VerticalComp(List.of(list, Comp.vspacer(5), add)).apply(struc -> struc.get().setFillWidth(true));
|
||||
return vbox.styleClass("data-store-list-choice-comp").createRegion();
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DynamicOptionsComp extends Comp<CompStructure<Pane>> {
|
||||
|
||||
private final List<Entry> entries;
|
||||
private final boolean wrap;
|
||||
|
||||
public DynamicOptionsComp(List<Entry> entries, boolean wrap) {
|
||||
this.entries = entries;
|
||||
this.wrap = wrap;
|
||||
}
|
||||
|
||||
public Entry queryEntry(String key) {
|
||||
return entries.stream()
|
||||
.filter(entry -> entry.key != null && entry.key.equals(key))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<Pane> createBase() {
|
||||
Pane pane;
|
||||
if (wrap) {
|
||||
var content = new FlowPane(Orientation.HORIZONTAL);
|
||||
content.setAlignment(Pos.CENTER);
|
||||
content.setHgap(14);
|
||||
content.setVgap(7);
|
||||
pane = content;
|
||||
} else {
|
||||
var content = new VBox();
|
||||
content.setSpacing(7);
|
||||
pane = content;
|
||||
}
|
||||
|
||||
var nameRegions = new ArrayList<Region>();
|
||||
var compRegions = new ArrayList<Region>();
|
||||
|
||||
for (var entry : getEntries()) {
|
||||
Region compRegion = null;
|
||||
if (entry.comp() != null) {
|
||||
compRegion = entry.comp().createRegion();
|
||||
}
|
||||
|
||||
if (entry.name() != null) {
|
||||
var line = new HBox();
|
||||
line.setFillHeight(true);
|
||||
if (!wrap) {
|
||||
line.prefWidthProperty().bind(pane.widthProperty());
|
||||
}
|
||||
line.setSpacing(8);
|
||||
|
||||
var name = new Label();
|
||||
name.textProperty().bind(entry.name());
|
||||
name.prefHeightProperty().bind(line.heightProperty());
|
||||
name.setMinWidth(Region.USE_PREF_SIZE);
|
||||
name.setAlignment(Pos.CENTER_LEFT);
|
||||
if (compRegion != null) {
|
||||
name.visibleProperty().bind(PlatformThread.sync(compRegion.visibleProperty()));
|
||||
name.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
|
||||
}
|
||||
nameRegions.add(name);
|
||||
line.getChildren().add(name);
|
||||
|
||||
if (compRegion != null) {
|
||||
compRegions.add(compRegion);
|
||||
line.getChildren().add(compRegion);
|
||||
if (!wrap) {
|
||||
HBox.setHgrow(compRegion, Priority.ALWAYS);
|
||||
}
|
||||
}
|
||||
|
||||
pane.getChildren().add(line);
|
||||
} else {
|
||||
if (compRegion != null) {
|
||||
compRegions.add(compRegion);
|
||||
pane.getChildren().add(compRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wrap) {
|
||||
var compWidthBinding = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
if (compRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
return compRegions.stream()
|
||||
.map(Region::getWidth)
|
||||
.max(Double::compareTo)
|
||||
.orElse(0.0);
|
||||
},
|
||||
compRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
compRegions.forEach(r -> r.prefWidthProperty().bind(compWidthBinding));
|
||||
}
|
||||
|
||||
if (entries.stream().anyMatch(entry -> entry.name() != null)) {
|
||||
var nameWidthBinding = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
if (nameRegions.stream().anyMatch(r -> r.getWidth() == 0)) {
|
||||
return Region.USE_COMPUTED_SIZE;
|
||||
}
|
||||
|
||||
return nameRegions.stream()
|
||||
.map(Region::getWidth)
|
||||
.max(Double::compareTo)
|
||||
.orElse(0.0);
|
||||
},
|
||||
nameRegions.stream().map(Region::widthProperty).toList().toArray(new Observable[0]));
|
||||
nameRegions.forEach(r -> r.prefWidthProperty().bind(nameWidthBinding));
|
||||
}
|
||||
|
||||
return new SimpleCompStructure<>(pane);
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
public record Entry(String key, ObservableValue<String> name, Comp<?> comp) {}
|
||||
}
|
|
@ -5,7 +5,6 @@ import io.xpipe.app.browser.StandaloneFileBrowser;
|
|||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.core.store.FileSystemStore;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -22,10 +21,6 @@ public class FileStoreChoiceComp extends SimpleComp {
|
|||
private final Property<FileSystemStore> fileSystem;
|
||||
private final Property<String> filePath;
|
||||
|
||||
public FileStoreChoiceComp(Property<String> filePath) {
|
||||
this(true, new SimpleObjectProperty<>(), filePath);
|
||||
}
|
||||
|
||||
public FileStoreChoiceComp(boolean hideFileSystem, Property<FileSystemStore> fileSystem, Property<String> filePath) {
|
||||
this.hideFileSystem = hideFileSystem;
|
||||
this.fileSystem = fileSystem != null ? fileSystem : new SimpleObjectProperty<>();
|
||||
|
@ -46,7 +41,7 @@ public class FileStoreChoiceComp extends SimpleComp {
|
|||
.grow(false, true);
|
||||
|
||||
var fileBrowseButton = new ButtonComp(null, new FontIcon("mdi2f-folder-open-outline"), () -> {
|
||||
StandaloneFileBrowser.openSingleFile(() -> (ShellStore) fileSystem.getValue(), fileStore -> {
|
||||
StandaloneFileBrowser.openSingleFile(() -> null, fileStore -> {
|
||||
if (fileStore == null) {
|
||||
filePath.setValue(null);
|
||||
fileSystem.setValue(null);
|
||||
|
|
|
@ -16,6 +16,10 @@ public class HorizontalComp extends Comp<CompStructure<HBox>> {
|
|||
entries = List.copyOf(comps);
|
||||
}
|
||||
|
||||
public Comp<CompStructure<HBox>> spacing(double spacing) {
|
||||
return apply(struc -> struc.get().setSpacing(spacing));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<HBox> createBase() {
|
||||
HBox b = new HBox();
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
|
|
@ -2,14 +2,14 @@ package io.xpipe.app.fxcomps.impl;
|
|||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
public class PrettyImageHelper {
|
||||
|
||||
public static Comp<?> ofFixedSquare(String img, int size) {
|
||||
if (img.endsWith(".svg")) {
|
||||
if (img != null && img.endsWith(".svg")) {
|
||||
var base = FileNames.getBaseName(img);
|
||||
var renderedName = base + "-" + size + ".png";
|
||||
if (AppImages.hasNormalImage(base + "-" + size + ".png")) {
|
||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.app.fxcomps.SimpleComp;
|
|||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.util.SimpleValidator;
|
||||
import io.xpipe.app.util.Validatable;
|
||||
import io.xpipe.app.util.Validator;
|
||||
import io.xpipe.core.source.WriteMode;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import net.synedra.validatorfx.Check;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class WriteModeChoiceComp extends SimpleComp implements Validatable {
|
||||
|
||||
Property<WriteMode> selected;
|
||||
ObservableList<WriteMode> available;
|
||||
Validator validator = new SimpleValidator();
|
||||
Check check;
|
||||
|
||||
public WriteModeChoiceComp(Property<WriteMode> selected, ObservableList<WriteMode> available) {
|
||||
this.selected = selected;
|
||||
this.available = available;
|
||||
if (available.size() == 1) {
|
||||
selected.setValue(available.get(0));
|
||||
}
|
||||
check = Validator.nonNull(validator, AppI18n.observable("mode"), selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var a = available;
|
||||
Property<Map<WriteMode, ObservableValue<String>>> map = new SimpleObjectProperty<>(new LinkedHashMap<>());
|
||||
for (WriteMode writeMode : a) {
|
||||
map.getValue().put(writeMode, AppI18n.observable(writeMode.getId()));
|
||||
}
|
||||
|
||||
PlatformThread.sync(available).addListener((ListChangeListener<? super WriteMode>) c -> {
|
||||
var newMap = new LinkedHashMap<WriteMode, ObservableValue<String>>();
|
||||
for (WriteMode writeMode : c.getList()) {
|
||||
newMap.put(writeMode, AppI18n.observable(writeMode.getId()));
|
||||
}
|
||||
map.setValue(newMap);
|
||||
|
||||
if (c.getList().size() == 1) {
|
||||
selected.setValue(c.getList().get(0));
|
||||
}
|
||||
});
|
||||
|
||||
return new ToggleGroupComp<>(selected, map)
|
||||
.apply(struc -> {
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
new FancyTooltipAugment<>(a.get(i).getId() + "Description")
|
||||
.augment(struc.get().getChildren().get(i));
|
||||
}
|
||||
})
|
||||
.apply(struc -> check.decorates(struc.get()))
|
||||
.createRegion();
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ public class BindingsHelper {
|
|||
public static <T,U> ObservableValue<U> map(ObservableValue<T> observableValue, Function<? super T, ? extends U> mapper) {
|
||||
return persist(Bindings.createObjectBinding(() -> {
|
||||
return mapper.apply(observableValue.getValue());
|
||||
},observableValue));
|
||||
}, observableValue));
|
||||
}
|
||||
|
||||
public static <T,U> ObservableValue<U> flatMap(ObservableValue<T> observableValue, Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
|
||||
|
@ -117,6 +117,12 @@ public class BindingsHelper {
|
|||
return prop;
|
||||
}
|
||||
|
||||
public static <T,U> ObservableValue<Boolean> anyMatch(List<? extends ObservableValue<Boolean>> l) {
|
||||
return BindingsHelper.persist(Bindings.createBooleanBinding(() -> {
|
||||
return l.stream().anyMatch(booleanObservableValue -> booleanObservableValue.getValue());
|
||||
}, l.toArray(ObservableValue[]::new)));
|
||||
}
|
||||
|
||||
public static <T, V> void bindMappedContent(ObservableList<T> l1, ObservableList<V> l2, Function<V, T> map) {
|
||||
Runnable runnable = () -> {
|
||||
setContent(l1, l2.stream().map(map).toList());
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
public class PlatformThread {
|
||||
|
||||
public static Observable sync(Observable o) {
|
||||
Objects.requireNonNull(o);
|
||||
return new Observable() {
|
||||
|
||||
private final Map<InvalidationListener, InvalidationListener> invListenerMap = new ConcurrentHashMap<>();
|
||||
|
@ -40,6 +41,7 @@ public class PlatformThread {
|
|||
}
|
||||
|
||||
public static <T> ObservableValue<T> sync(ObservableValue<T> ov) {
|
||||
Objects.requireNonNull(ov);
|
||||
ObservableValue<T> obs = new ObservableValue<>() {
|
||||
|
||||
private final Map<ChangeListener<? super T>, ChangeListener<? super T>> changeListenerMap =
|
||||
|
@ -86,6 +88,7 @@ public class PlatformThread {
|
|||
}
|
||||
|
||||
public static <T> ObservableList<T> sync(ObservableList<T> ol) {
|
||||
Objects.requireNonNull(ol);
|
||||
ObservableList<T> obs = new ObservableList<>() {
|
||||
|
||||
private final Map<ListChangeListener<? super T>, ListChangeListener<? super T>> listChangeListenerMap =
|
||||
|
|
|
@ -4,11 +4,13 @@ import io.xpipe.app.comp.base.ButtonComp;
|
|||
import io.xpipe.app.comp.base.TitledPaneComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.core.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
||||
import io.xpipe.app.util.JfxHelper;
|
||||
import io.xpipe.app.util.LicenseException;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
|
@ -192,6 +194,26 @@ public class ErrorHandlerComp extends SimpleComp {
|
|||
actionBox.getStyleClass().add("actions");
|
||||
actionBox.setFillWidth(true);
|
||||
|
||||
if (event.getThrowable() instanceof LicenseException) {
|
||||
event.getCustomActions().add(new ErrorAction() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return AppI18n.get("upgrade");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return AppI18n.get("seeTiers");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ErrorEvent event) {
|
||||
AppLayoutModel.get().selectLicense();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var custom = event.getCustomActions();
|
||||
for (var c : custom) {
|
||||
var ac = createActionComp(c);
|
||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.app.core.mode.OperationMode;
|
|||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -116,7 +116,7 @@ public abstract class LauncherInput {
|
|||
|
||||
var dir = Files.isDirectory(file) ? file : file.getParent();
|
||||
AppLayoutModel.get().selectBrowser();
|
||||
BrowserModel.DEFAULT.openFileSystemAsync(null, ShellStore.createLocal(), dir.toString(), null);
|
||||
BrowserModel.DEFAULT.openFileSystemAsync( DataStorage.get().local().ref(), dir.toString(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,7 +19,7 @@ import io.xpipe.app.fxcomps.impl.StackComp;
|
|||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
import io.xpipe.core.util.SecretValue;
|
||||
import javafx.beans.binding.Bindings;
|
||||
|
@ -198,10 +198,6 @@ public class AppPrefs {
|
|||
private final BooleanField automaticallyCheckForUpdatesField =
|
||||
BooleanField.ofBooleanType(automaticallyCheckForUpdates).render(() -> new CustomToggleControl());
|
||||
|
||||
private final BooleanProperty checkForPrereleases = typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||
private final BooleanField checkForPrereleasesField =
|
||||
BooleanField.ofBooleanType(checkForPrereleases).render(() -> new CustomToggleControl());
|
||||
|
||||
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
|
||||
|
||||
// Storage
|
||||
|
@ -309,10 +305,6 @@ public class AppPrefs {
|
|||
return automaticallyCheckForUpdates;
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty updateToPrereleases() {
|
||||
return checkForPrereleases;
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty confirmDeletions() {
|
||||
return confirmDeletions;
|
||||
}
|
||||
|
@ -569,16 +561,15 @@ public class AppPrefs {
|
|||
"appBehaviour",
|
||||
Setting.of("startupBehaviour", startupBehaviourControl, startupBehaviour),
|
||||
Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)),
|
||||
Group.of(
|
||||
"advanced",
|
||||
Setting.of("developerMode", developerModeField, internalDeveloperMode)),
|
||||
Group.of(
|
||||
"updates",
|
||||
Setting.of(
|
||||
"automaticallyUpdate",
|
||||
automaticallyCheckForUpdatesField,
|
||||
automaticallyCheckForUpdates),
|
||||
Setting.of("updateToPrereleases", checkForPrereleasesField, checkForPrereleases)),
|
||||
Group.of(
|
||||
"advanced",
|
||||
Setting.of("developerMode", developerModeField, internalDeveloperMode))),
|
||||
automaticallyCheckForUpdates))),
|
||||
new VaultCategory(this).create(),
|
||||
Category.of(
|
||||
"appearance",
|
||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.prefs;
|
|||
|
||||
import io.xpipe.app.ext.PrefsChoiceValue;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
|
|
|
@ -6,8 +6,8 @@ import io.xpipe.app.util.ApplicationHelper;
|
|||
import io.xpipe.app.util.MacOsPermissions;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.process.CommandBuilder;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
|
@ -170,7 +170,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
@Override
|
||||
public void launch(String name, String file) throws Exception {
|
||||
try (ShellControl pc = LocalStore.getShell()) {
|
||||
ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null);
|
||||
ApplicationHelper.isInPath(pc, executable, toTranslatedString(), null);
|
||||
|
||||
var toExecute = executable + " " + toCommand(name, file).build(pc);
|
||||
// In order to fix this bug which also affects us:
|
||||
|
@ -590,7 +590,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
|||
@Override
|
||||
public void launch(String name, String file) throws Exception {
|
||||
try (ShellControl pc = LocalStore.getShell()) {
|
||||
ApplicationHelper.checkSupport(pc, executable, toTranslatedString(), null);
|
||||
ApplicationHelper.isInPath(pc, executable, toTranslatedString(), null);
|
||||
|
||||
var toExecute = executable + " " + toCommand(name, file).build(pc);
|
||||
if (pc.getOsType().equals(OsType.WINDOWS)) {
|
||||
|
|
|
@ -51,7 +51,7 @@ public class JsonStorageHandler implements StorageHandler {
|
|||
var id = getSaveId(breadcrumb);
|
||||
var tree = object instanceof PrefsChoiceValue prefsChoiceValue
|
||||
? new TextNode(prefsChoiceValue.getId())
|
||||
: (object != null ? JacksonMapper.newMapper().valueToTree(object) : NullNode.getInstance());
|
||||
: (object != null ? JacksonMapper.getDefault().valueToTree(object) : NullNode.getInstance());
|
||||
setContent(id, tree);
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ public class JsonStorageHandler implements StorageHandler {
|
|||
|
||||
try {
|
||||
TrackEvent.debug("Loading preferences value for key " + breadcrumb + " from value " + tree);
|
||||
return JacksonMapper.newMapper().treeToValue(tree, type);
|
||||
return JacksonMapper.getDefault().treeToValue(tree, type);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
return defaultObject;
|
||||
|
|
|
@ -10,7 +10,7 @@ import io.xpipe.app.fxcomps.impl.HorizontalComp;
|
|||
import io.xpipe.app.fxcomps.impl.TextFieldComp;
|
||||
import io.xpipe.app.util.TerminalHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.process.CommandControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import javafx.beans.property.Property;
|
||||
|
|
|
@ -9,9 +9,9 @@ import io.xpipe.app.fxcomps.impl.VerticalComp;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.UserReportComp;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.ShellStore;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -58,7 +58,7 @@ public class TroubleshootComp extends Comp<CompStructure<?>> {
|
|||
.addComp(
|
||||
new TileButtonComp("launchDebugMode", "launchDebugModeDescription", "mdmz-refresh", e -> {
|
||||
OperationMode.executeAfterShutdown(() -> {
|
||||
try (var sc = ShellStore.createLocal()
|
||||
try (var sc = new LocalStore()
|
||||
.control()
|
||||
.start()) {
|
||||
var script = FileNames.join(
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package io.xpipe.app.storage;
|
||||
|
||||
import io.xpipe.core.store.StatefulDataStore;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.DataStoreState;
|
||||
import io.xpipe.core.util.DataStateProvider;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DataStateProviderImpl extends DataStateProvider {
|
||||
|
||||
@Override
|
||||
public void putState(DataStore store, String key, Object value) {
|
||||
public void setState(DataStore store, Object value) {
|
||||
if (DataStorage.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -21,14 +22,12 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
var old = entry.get().getElementState().put(key, value);
|
||||
if (!Objects.equals(old, value)) {
|
||||
entry.get().simpleRefresh();
|
||||
}
|
||||
entry.get().setStorePersistentState(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getState(DataStore store, String key, Class<T> c, Supplier<T> def) {
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends DataStoreState> T getState(DataStore store, Supplier<T> def) {
|
||||
if (DataStorage.get() == null) {
|
||||
return def.get();
|
||||
}
|
||||
|
@ -38,7 +37,43 @@ public class DataStateProviderImpl extends DataStateProvider {
|
|||
return def.get();
|
||||
}
|
||||
|
||||
var result = entry.get().getElementState().computeIfAbsent(key, k -> def.get());
|
||||
if (!(store instanceof StatefulDataStore<?> sds)) {
|
||||
return def.get();
|
||||
}
|
||||
|
||||
var found = entry.get().getStorePersistentState();
|
||||
if (found == null) {
|
||||
entry.get().setStorePersistentState(def.get());
|
||||
}
|
||||
return (T) entry.get().getStorePersistentState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putCache(DataStore store, String key, Object value) {
|
||||
if (DataStorage.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
if (entry.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var old = entry.get().getStoreCache().put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getCache(DataStore store, String key, Class<T> c, Supplier<T> def) {
|
||||
if (DataStorage.get() == null) {
|
||||
return def.get();
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(store);
|
||||
if (entry.isEmpty()) {
|
||||
return def.get();
|
||||
}
|
||||
|
||||
var result = entry.get().getStoreCache().computeIfAbsent(key, k -> def.get());
|
||||
return c.cast(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ package io.xpipe.app.storage;
|
|||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.FixedHierarchyStore;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.impl.LocalStore;
|
||||
import io.xpipe.core.source.DataStoreId;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.store.DataStoreId;
|
||||
import io.xpipe.core.store.FixedChildStore;
|
||||
import io.xpipe.core.store.FixedHierarchyStore;
|
||||
import io.xpipe.core.store.LocalStore;
|
||||
import io.xpipe.core.util.FailableRunnable;
|
||||
import javafx.util.Pair;
|
||||
import lombok.Getter;
|
||||
|
@ -18,12 +18,18 @@ import lombok.Setter;
|
|||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class DataStorage {
|
||||
|
||||
public static final UUID ALL_CATEGORY_UUID = UUID.fromString("bfb0b51a-e7a3-4ce4-8878-8d4cb5828d6c");
|
||||
public static final UUID SCRIPTS_CATEGORY_UUID = UUID.fromString("19024cf9-d192-41a9-88a6-a22694cf716a");
|
||||
public static final UUID PREDEFINED_SCRIPTS_CATEGORY_UUID = UUID.fromString("5faf1d71-0efc-4293-8b70-299406396973");
|
||||
public static final UUID CUSTOM_SCRIPTS_CATEGORY_UUID = UUID.fromString("d3496db5-b709-41f9-abc0-ee0a660fbab9");
|
||||
public static final UUID DEFAULT_CATEGORY_UUID = UUID.fromString("97458c07-75c0-4f9d-a06e-92d8cdf67c40");
|
||||
public static final UUID LOCAL_ID = UUID.fromString("f0ec68aa-63f5-405c-b178-9a4454556d6b");
|
||||
|
||||
private static final String PERSIST_PROP = "io.xpipe.storage.persist";
|
||||
private static final String IMMUTABLE_PROP = "io.xpipe.storage.immutable";
|
||||
|
||||
|
@ -45,11 +51,23 @@ public abstract class DataStorage {
|
|||
this.storeCategories = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
|
||||
public DataStoreCategory getDefaultCategory(){
|
||||
protected void refreshValidities(boolean makeValid) {
|
||||
var changed = new AtomicBoolean(false);
|
||||
do {
|
||||
changed.set(false);
|
||||
storeEntries.forEach(dataStoreEntry -> {
|
||||
if (makeValid ? dataStoreEntry.tryMakeValid() : dataStoreEntry.tryMakeInvalid()) {
|
||||
changed.set(true);
|
||||
}
|
||||
});
|
||||
} while (changed.get());
|
||||
}
|
||||
|
||||
public DataStoreCategory getDefaultCategory() {
|
||||
return getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).orElseThrow();
|
||||
}
|
||||
|
||||
public DataStoreCategory getAllCategory(){
|
||||
public DataStoreCategory getAllCategory() {
|
||||
return getStoreCategoryIfPresent(ALL_CATEGORY_UUID).orElseThrow();
|
||||
}
|
||||
|
||||
|
@ -69,8 +87,6 @@ public abstract class DataStorage {
|
|||
INSTANCE = shouldPersist() ? new StandardStorage() : new ImpersistentStorage();
|
||||
INSTANCE.load();
|
||||
|
||||
INSTANCE.storeEntries.forEach(entry -> entry.simpleRefresh());
|
||||
|
||||
DataStoreProviders.getAll().forEach(dataStoreProvider -> {
|
||||
try {
|
||||
dataStoreProvider.storageInit();
|
||||
|
@ -94,10 +110,6 @@ public abstract class DataStorage {
|
|||
}
|
||||
|
||||
public boolean refreshChildren(DataStoreEntry e) {
|
||||
return refreshChildren(e, null);
|
||||
}
|
||||
|
||||
public boolean refreshChildren(DataStoreEntry e, DataStore newValue) {
|
||||
if (!(e.getStore() instanceof FixedHierarchyStore)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -105,7 +117,7 @@ public abstract class DataStorage {
|
|||
e.setInRefresh(true);
|
||||
Map<String, FixedChildStore> newChildren;
|
||||
try {
|
||||
newChildren = ((FixedHierarchyStore) (newValue != null ? newValue : e.getStore())).listChildren();
|
||||
newChildren = ((FixedHierarchyStore) (e.getStore())).listChildren(e);
|
||||
e.setInRefresh(false);
|
||||
} catch (Exception ex) {
|
||||
e.setInRefresh(false);
|
||||
|
@ -113,12 +125,11 @@ public abstract class DataStorage {
|
|||
return false;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
var oldChildren = getStoreChildren(e, false, false);
|
||||
var oldChildren = getStoreChildren(e, false);
|
||||
var toRemove = oldChildren.stream()
|
||||
.filter(entry -> newChildren.entrySet().stream()
|
||||
.noneMatch(nc ->
|
||||
nc.getValue().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()))
|
||||
.noneMatch(
|
||||
nc -> nc.getValue().getFixedId() == ((FixedChildStore) entry.getStore()).getFixedId()))
|
||||
.toList();
|
||||
var toAdd = newChildren.entrySet().stream()
|
||||
.filter(entry -> oldChildren.stream()
|
||||
|
@ -136,12 +147,12 @@ public abstract class DataStorage {
|
|||
.filter(en -> en.getValue() != null)
|
||||
.toList();
|
||||
|
||||
if (newValue != null) {
|
||||
e.setStoreInternal(newValue, false);
|
||||
if (newChildren.size() > 0) {
|
||||
e.setExpanded(true);
|
||||
}
|
||||
|
||||
deleteWithChildren(toRemove.toArray(DataStoreEntry[]::new));
|
||||
addStoreEntries(toAdd.stream()
|
||||
addStoreEntriesIfNotPresent(toAdd.stream()
|
||||
.map(stringDataStoreEntry -> DataStoreEntry.createNew(
|
||||
UUID.randomUUID(),
|
||||
e.getCategoryUuid(),
|
||||
|
@ -158,67 +169,90 @@ public abstract class DataStorage {
|
|||
saveAsync();
|
||||
return !newChildren.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteWithChildren(DataStoreEntry... entries) {
|
||||
var toDelete = Arrays.stream(entries)
|
||||
.flatMap(entry -> {
|
||||
// Reverse to delete deepest children first
|
||||
var ordered = getStoreChildren(entry, false, true);
|
||||
var ordered = getStoreChildren(entry, true);
|
||||
Collections.reverse(ordered);
|
||||
ordered.add(entry);
|
||||
return ordered.stream();
|
||||
})
|
||||
.toList();
|
||||
|
||||
synchronized (this) {
|
||||
toDelete.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntries.removeAll(toDelete);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(toDelete.toArray(DataStoreEntry[]::new)));
|
||||
}
|
||||
refreshValidities(false);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public void deleteChildren(DataStoreEntry e, boolean deep) {
|
||||
// Reverse to delete deepest children first
|
||||
var ordered = getStoreChildren(e, true, deep);
|
||||
var ordered = getStoreChildren(e, deep);
|
||||
Collections.reverse(ordered);
|
||||
|
||||
synchronized (this) {
|
||||
ordered.forEach(entry -> entry.finalizeEntry());
|
||||
this.storeEntries.removeAll(ordered);
|
||||
this.listeners.forEach(l -> l.onStoreRemove(ordered.toArray(DataStoreEntry[]::new)));
|
||||
}
|
||||
refreshValidities(false);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public Optional<DataStoreEntry> getParent(DataStoreEntry entry, boolean display) {
|
||||
if (entry.getState() == DataStoreEntry.State.LOAD_FAILED) {
|
||||
public Optional<DataStoreEntry> getDisplayParent(DataStoreEntry entry) {
|
||||
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
var provider = entry.getProvider();
|
||||
var parent =
|
||||
display ? provider.getDisplayParent(entry.getStore()) : provider.getLogicalParent(entry.getStore());
|
||||
return parent != null ? getStoreEntryIfPresent(parent) : Optional.empty();
|
||||
return Optional.ofNullable(provider.getDisplayParent(entry)).filter(dataStoreEntry -> storeEntries.contains(dataStoreEntry));
|
||||
} catch (Exception ex) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean display, boolean deep) {
|
||||
if (entry.getState() == DataStoreEntry.State.LOAD_FAILED) {
|
||||
public Optional<DataStoreEntry> findEntry(DataStore store) {
|
||||
if (store == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
for (DataStoreEntry entry : storeEntries) {
|
||||
if (entry.getStore() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.getStore()
|
||||
.getClass()
|
||||
.equals(store.getClass())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.getStore().equals(store)) {
|
||||
return Optional.of(entry);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public List<DataStoreEntry> getStoreChildren(DataStoreEntry entry, boolean deep) {
|
||||
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var children = new ArrayList<>(getStoreEntries().stream()
|
||||
var entries = getStoreEntries();
|
||||
if (!entries.contains(entry)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
var children = new ArrayList<>(entries.stream()
|
||||
.filter(other -> {
|
||||
if (other.getState() == DataStoreEntry.State.LOAD_FAILED) {
|
||||
if (other.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = getParent(other, display);
|
||||
var parent = getDisplayParent(other);
|
||||
return parent.isPresent()
|
||||
&& entry.getStore()
|
||||
.getClass()
|
||||
|
@ -229,7 +263,7 @@ public abstract class DataStorage {
|
|||
|
||||
if (deep) {
|
||||
for (DataStoreEntry dataStoreEntry : new ArrayList<>(children)) {
|
||||
children.addAll(getStoreChildren(dataStoreEntry, display, true));
|
||||
children.addAll(getStoreChildren(dataStoreEntry, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -258,22 +292,14 @@ public abstract class DataStorage {
|
|||
return dir.resolve("categories");
|
||||
}
|
||||
|
||||
public synchronized List<DataStore> getUsableStores() {
|
||||
public List<DataStore> getUsableStores() {
|
||||
return new ArrayList<>(getStoreEntries().stream()
|
||||
.filter(entry -> entry.getState().isUsable())
|
||||
.filter(entry -> entry.getValidity().isUsable())
|
||||
.map(DataStoreEntry::getStore)
|
||||
.toList());
|
||||
}
|
||||
|
||||
public synchronized void renameStoreEntry(DataStoreEntry entry, String name) {
|
||||
if (getStoreEntryIfPresent(name).isPresent()) {
|
||||
throw new IllegalArgumentException("Store with name " + name + " already exists");
|
||||
}
|
||||
|
||||
entry.setName(name);
|
||||
}
|
||||
|
||||
public synchronized DataStoreEntry getStoreEntry(@NonNull String name, boolean acceptDisabled) {
|
||||
public DataStoreEntry getStoreEntry(@NonNull String name, boolean acceptDisabled) {
|
||||
var entry = storeEntries.stream()
|
||||
.filter(n -> n.getName().equalsIgnoreCase(name))
|
||||
.findFirst()
|
||||
|
@ -286,15 +312,15 @@ public abstract class DataStorage {
|
|||
|
||||
public DataStoreId getId(DataStoreEntry entry) {
|
||||
var names = new ArrayList<String>();
|
||||
names.add(entry.getName());
|
||||
names.add(entry.getName().replaceAll(":", "_"));
|
||||
|
||||
DataStoreEntry current = entry;
|
||||
while ((current = getParent(current, false).orElse(null)) != null) {
|
||||
while ((current = getDisplayParent(current).orElse(null)) != null) {
|
||||
if (new LocalStore().equals(current.getStore())) {
|
||||
break;
|
||||
}
|
||||
|
||||
names.add(0, current.getName());
|
||||
names.add(0, current.getName().replaceAll(":", "_"));
|
||||
}
|
||||
|
||||
return DataStoreId.create(names.toArray(String[]::new));
|
||||
|
@ -313,7 +339,7 @@ public abstract class DataStorage {
|
|||
var current = getStoreEntryIfPresent(id.getNames().get(0));
|
||||
if (current.isPresent()) {
|
||||
for (int i = 1; i < id.getNames().size(); i++) {
|
||||
var children = getStoreChildren(current.get(), false, false);
|
||||
var children = getStoreChildren(current.get(), false);
|
||||
int finalI = i;
|
||||
current = children.stream()
|
||||
.filter(dataStoreEntry -> dataStoreEntry
|
||||
|
@ -344,6 +370,7 @@ public abstract class DataStorage {
|
|||
|
||||
public abstract boolean supportsSharing();
|
||||
|
||||
|
||||
public Optional<DataStoreCategory> getStoreCategoryIfPresent(UUID uuid) {
|
||||
if (uuid == null) {
|
||||
return Optional.empty();
|
||||
|
@ -362,40 +389,30 @@ public abstract class DataStorage {
|
|||
.findFirst();
|
||||
}
|
||||
|
||||
public boolean setAndRefresh(DataStoreEntry entry, DataStore s) {
|
||||
var old = entry.getStore();
|
||||
deleteChildren(entry, true);
|
||||
try {
|
||||
entry.setStoreInternal(s, false);
|
||||
entry.refresh(true);
|
||||
return DataStorage.get().refreshChildren(entry, s);
|
||||
} catch (Exception e) {
|
||||
entry.setStoreInternal(old, false);
|
||||
entry.simpleRefresh();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) {
|
||||
var oldParent = DataStorage.get().getParent(entry, false);
|
||||
var newParent = DataStorage.get().getParent(newEntry, false);
|
||||
var oldParent = DataStorage.get().getDisplayParent(entry);
|
||||
var newParent = DataStorage.get().getDisplayParent(newEntry);
|
||||
var diffParent = Objects.equals(oldParent, newParent);
|
||||
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
newEntry.finalizeEntry();
|
||||
|
||||
var children = getStoreChildren(entry, false, true);
|
||||
if (!Objects.equals(oldParent, newParent)) {
|
||||
var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
var children = getStoreChildren(entry, true);
|
||||
if (!diffParent) {
|
||||
var toRemove = Stream.concat(Stream.of(entry), children.stream())
|
||||
.toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||
}
|
||||
|
||||
entry.applyChanges(newEntry);
|
||||
entry.initializeEntry();
|
||||
|
||||
if (!Objects.equals(oldParent, newParent)) {
|
||||
var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
if (!diffParent) {
|
||||
var toAdd = Stream.concat(Stream.of(entry), children.stream())
|
||||
.toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||
refreshValidities(true);
|
||||
}
|
||||
},
|
||||
entry);
|
||||
|
@ -404,79 +421,99 @@ public abstract class DataStorage {
|
|||
public void updateCategory(DataStoreEntry entry, DataStoreCategory newCategory) {
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
var children = getStoreChildren(entry, false, true);
|
||||
var toRemove = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
var children = getStoreChildren(entry, true);
|
||||
var toRemove =
|
||||
Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreRemove(toRemove));
|
||||
|
||||
entry.setCategoryUuid(newCategory.getUuid());
|
||||
children.forEach(child -> child.setCategoryUuid(newCategory.getUuid()));
|
||||
|
||||
var toAdd = Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
var toAdd =
|
||||
Stream.concat(Stream.of(entry), children.stream()).toArray(DataStoreEntry[]::new);
|
||||
listeners.forEach(storageListener -> storageListener.onStoreAdd(toAdd));
|
||||
},
|
||||
entry);
|
||||
}
|
||||
|
||||
public boolean refresh(DataStoreEntry element, boolean deep) {
|
||||
if (element.isInRefresh()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
propagateUpdate(() -> element.refresh(deep), element);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).reportable(false).handle();
|
||||
return false;
|
||||
}
|
||||
saveAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void refreshAsync(DataStoreEntry element, boolean deep) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
propagateUpdate(() -> element.refresh(deep), element);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).reportable(false).handle();
|
||||
}
|
||||
saveAsync();
|
||||
});
|
||||
}
|
||||
|
||||
<T extends Throwable> void propagateUpdate(FailableRunnable<T> runnable, DataStoreEntry origin) throws T {
|
||||
var children = getStoreChildren(origin, false, true);
|
||||
var children = getStoreChildren(origin, true);
|
||||
runnable.run();
|
||||
children.forEach(entry -> {
|
||||
entry.simpleRefresh();
|
||||
entry.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
public DataStoreCategory addStoreCategoryIfNotPresent(@NonNull DataStoreCategory cat) {
|
||||
if (storeCategories.contains(cat)) {
|
||||
return cat;
|
||||
}
|
||||
|
||||
var byId = getStoreCategoryIfPresent(cat.getUuid()).orElse(null);
|
||||
if (byId != null) {
|
||||
return byId;
|
||||
}
|
||||
|
||||
addStoreCategory(cat);
|
||||
return cat;
|
||||
}
|
||||
|
||||
public void addStoreCategory(@NonNull DataStoreCategory cat) {
|
||||
synchronized (this) {
|
||||
cat.setDirectory(getCategoriesDir().resolve(cat.getUuid().toString()));
|
||||
this.storeCategories.add(cat);
|
||||
}
|
||||
saveAsync();
|
||||
|
||||
this.listeners.forEach(l -> l.onCategoryAdd(cat));
|
||||
}
|
||||
|
||||
public void addStoreEntry(@NonNull DataStoreEntry e) {
|
||||
e.getProvider().preAdd(e.getStore());
|
||||
synchronized (this) {
|
||||
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull DataStoreEntry e) {
|
||||
if (storeEntries.contains(e)) {
|
||||
return e;
|
||||
}
|
||||
|
||||
var byId = getStoreEntryIfPresent(e.getUuid()).orElse(null);
|
||||
if (byId != null) {
|
||||
return byId;
|
||||
}
|
||||
|
||||
if (e.getValidity().isUsable()) {
|
||||
var displayParent = e.getProvider().getDisplayParent(e);
|
||||
if (displayParent != null) {
|
||||
displayParent.setExpanded(true);
|
||||
addStoreEntryIfNotPresent(displayParent);
|
||||
}
|
||||
}
|
||||
|
||||
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
|
||||
this.storeEntries.add(e);
|
||||
}
|
||||
saveAsync();
|
||||
|
||||
this.listeners.forEach(l -> l.onStoreAdd(e));
|
||||
e.initializeEntry();
|
||||
refreshValidities(true);
|
||||
return e;
|
||||
}
|
||||
|
||||
public void addStoreEntries(@NonNull DataStoreEntry... es) {
|
||||
synchronized (this) {
|
||||
public DataStoreEntry getOrCreateNewEntry(String name, DataStore store) {
|
||||
var found = findEntry(store);
|
||||
if (found.isPresent()) {
|
||||
return found.get();
|
||||
}
|
||||
|
||||
return DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store);
|
||||
}
|
||||
|
||||
public void addStoreEntriesIfNotPresent(@NonNull DataStoreEntry... es) {
|
||||
for (DataStoreEntry e : es) {
|
||||
e.getProvider().preAdd(e.getStore());
|
||||
if (storeEntries.contains(e) || findEntry(e.getStore()).isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var displayParent = e.getProvider().getDisplayParent(e);
|
||||
if (displayParent != null) {
|
||||
addStoreEntryIfNotPresent(displayParent);
|
||||
}
|
||||
|
||||
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
|
||||
this.storeEntries.add(e);
|
||||
}
|
||||
|
@ -484,24 +521,18 @@ public abstract class DataStorage {
|
|||
for (DataStoreEntry e : es) {
|
||||
e.initializeEntry();
|
||||
}
|
||||
}
|
||||
refreshValidities(true);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public DataStoreEntry addStoreEntryIfNotPresent(@NonNull String name, DataStore store) {
|
||||
var found = getStoreEntryIfPresent(store);
|
||||
if (found.isPresent()) {
|
||||
return found.get();
|
||||
public DataStoreEntry addStoreIfNotPresent(@NonNull String name, DataStore store) {
|
||||
var f = findEntry(store);
|
||||
if (f.isPresent()) {
|
||||
return f.get();
|
||||
}
|
||||
|
||||
var e = DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store);
|
||||
addStoreEntry(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
public DataStoreEntry addStoreEntry(@NonNull String name, DataStore store) {
|
||||
var e = DataStoreEntry.createNew(UUID.randomUUID(), selectedCategory.getUuid(), name, store);
|
||||
addStoreEntry(e);
|
||||
addStoreEntryIfNotPresent(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
@ -510,31 +541,27 @@ public abstract class DataStorage {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
return DataStorage.get().getStoreEntries().stream()
|
||||
.filter(entry -> !entry.isDisabled() && entry.getStore().equals(store))
|
||||
.findFirst()
|
||||
.map(entry -> entry.getName());
|
||||
return findEntry(store).map(dataStoreEntry -> dataStoreEntry.getName());
|
||||
}
|
||||
|
||||
public String getStoreBrowserDisplayName(DataStore store) {
|
||||
public String getStoreBrowserDisplayName(DataStoreEntry store) {
|
||||
if (store == null) {
|
||||
return "?";
|
||||
}
|
||||
|
||||
return getStoreEntryIfPresent(store).map(entry -> entry.getProvider().browserDisplayName(store)).orElse("?");
|
||||
return store.getProvider().browserDisplayName(store.getStore());
|
||||
}
|
||||
|
||||
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
|
||||
propagateUpdate(
|
||||
() -> {
|
||||
store.finalizeEntry();
|
||||
synchronized (this) {
|
||||
this.storeEntries.remove(store);
|
||||
}
|
||||
},
|
||||
store);
|
||||
saveAsync();
|
||||
this.listeners.forEach(l -> l.onStoreRemove(store));
|
||||
refreshValidities(false);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
public void deleteStoreCategory(@NonNull DataStoreCategory cat) {
|
||||
|
@ -566,10 +593,18 @@ public abstract class DataStorage {
|
|||
|
||||
public abstract void save();
|
||||
|
||||
public Optional<DataStoreEntry> getStoreEntry(UUID id) {
|
||||
public Optional<DataStoreEntry> getStoreEntryIfPresent(UUID id) {
|
||||
return storeEntries.stream().filter(e -> e.getUuid().equals(id)).findAny();
|
||||
}
|
||||
|
||||
public DataStoreEntry getStoreEntry(UUID id) {
|
||||
return getStoreEntryIfPresent(id).orElseThrow();
|
||||
}
|
||||
|
||||
public DataStoreEntry local() {
|
||||
return getStoreEntryIfPresent(LOCAL_ID).orElse(null);
|
||||
}
|
||||
|
||||
public List<DataStoreEntry> getStoreEntries() {
|
||||
return new ArrayList<>(storeEntries);
|
||||
}
|
||||
|
|
|
@ -1,36 +1,14 @@
|
|||
package io.xpipe.app.storage;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.core.source.DataSource;
|
||||
import io.xpipe.core.store.DataStore;
|
||||
import io.xpipe.core.util.JacksonMapper;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class DataStorageParser {
|
||||
|
||||
public static DataSource<?> sourceFromNode(JsonNode node) {
|
||||
node = replaceReferenceIds(node);
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
try {
|
||||
return mapper.treeToValue(node, DataSource.class);
|
||||
} catch (Throwable e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static DataStore storeFromNode(JsonNode node) {
|
||||
node = replaceReferenceIds(node);
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
var mapper = JacksonMapper.getDefault();
|
||||
try {
|
||||
return mapper.treeToValue(node, DataStore.class);
|
||||
} catch (Throwable e) {
|
||||
|
@ -38,74 +16,4 @@ public class DataStorageParser {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode replaceReferenceIds(JsonNode node) {
|
||||
return replaceReferenceIds(node, new HashSet<>());
|
||||
}
|
||||
|
||||
private static JsonNode replaceReferenceIds(JsonNode node, Set<UUID> seenIds) {
|
||||
var mapper = JacksonMapper.newMapper();
|
||||
|
||||
node = replaceReferenceIdsForType(node, "storeId", id -> {
|
||||
if (seenIds.contains(id)) {
|
||||
TrackEvent.withWarn("storage", "Encountered cycle").tag("id", id);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntry(id);
|
||||
if (entry.isEmpty()) {
|
||||
TrackEvent.withWarn("storage", "Encountered unknown store").tag("id", id);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var testNode = entry.get().getResolvedNode();
|
||||
if (testNode == null) {
|
||||
TrackEvent.withWarn("storage", "Encountered disabled store").tag("id", id);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var newSeenIds = new HashSet<>(seenIds);
|
||||
newSeenIds.add(id);
|
||||
return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds));
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static JsonNode replaceReferenceIdsForType(
|
||||
JsonNode node, String replacementKeyName, Function<UUID, Optional<JsonNode>> function) {
|
||||
var value = getReferenceIdFromNode(node, replacementKeyName).orElse(null);
|
||||
if (value != null) {
|
||||
var found = function.apply(value);
|
||||
if (found.isEmpty() || found.get().isNull()) {
|
||||
TrackEvent.withWarn("storage", "Encountered unknown reference").tag("id", value);
|
||||
}
|
||||
|
||||
return found.orElseGet(NullNode::getInstance);
|
||||
}
|
||||
|
||||
if (!node.isObject()) {
|
||||
return node;
|
||||
}
|
||||
|
||||
var replacement = JsonNodeFactory.instance.objectNode();
|
||||
var iterator = node.fields();
|
||||
while (iterator.hasNext()) {
|
||||
var stringJsonNodeEntry = iterator.next();
|
||||
var resolved = replaceReferenceIdsForType(stringJsonNodeEntry.getValue(), replacementKeyName, function);
|
||||
replacement.set(stringJsonNodeEntry.getKey(), resolved);
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
|
||||
private static Optional<UUID> getReferenceIdFromNode(JsonNode node, String key) {
|
||||
if (node.isObject()) {
|
||||
var found = node.get(key);
|
||||
if (found != null && found.isTextual()) {
|
||||
var id = UUID.fromString(found.textValue());
|
||||
return Optional.of(id);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue