Add dynamic tunnels

This commit is contained in:
crschnick 2023-07-18 11:53:46 +00:00
parent a218f9ac35
commit c8cf6aa3fb
28 changed files with 124 additions and 178 deletions

View file

@ -55,7 +55,15 @@ public class StoreCreationBarComp extends SimpleComp {
.shortcut(new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addDatabase"));
var box = new VerticalComp(List.of(newHostStore, newShellStore, newStreamStore, newDbStore))
var newTunnelStore = new ButtonComp(AppI18n.observable("addTunnel"), new FontIcon("mdi2v-vector-polyline-plus"), () -> {
GuiDsStoreCreator.showCreation(
v -> v.getDisplayCategory().equals(DataStoreProvider.DisplayCategory.TUNNEL));
})
.styleClass(Styles.FLAT)
.shortcut(new KeyCodeCombination(KeyCode.T, KeyCombination.SHORTCUT_DOWN))
.apply(new FancyTooltipAugment<>("addTunnel"));
var box = new VerticalComp(List.of(newHostStore, newShellStore, newStreamStore, newDbStore, newTunnelStore))
.apply(struc -> struc.get().setFillWidth(true));
box.apply(s -> AppFont.medium(s.get()));
var bar = box.createRegion();

View file

@ -197,6 +197,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
found.createAction(entry.getStore().asNeeded()).execute();
} else if (getEntry().getStore() instanceof FixedHierarchyStore) {
refreshWithChildrenAsync();
} else {
}
}

View file

@ -22,7 +22,7 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
public class DataStoreSelectorComp extends Comp<CompStructure<Button>> {
DataStoreProvider.DataCategory category;
DataStoreProvider.DisplayCategory category;
Property<DataStore> chosenStore;
@Override
@ -34,7 +34,7 @@ public class DataStoreSelectorComp extends Comp<CompStructure<Button>> {
"inProgress",
null,
null,
v -> v.getCategory().equals(category),
v -> v.getDisplayCategory().equals(category),
entry -> {
chosenStore.setValue(entry.getStore());
},

View file

@ -1,52 +0,0 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.DataSourceProvider;
import io.xpipe.app.ext.DataStoreProvider;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.TabPaneComp;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.store.DataStore;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
import java.util.List;
import java.util.function.Predicate;
public class DsDbStoreChooserComp extends SimpleComp {
private final Property<DataStore> input;
private final ObservableValue<DataSourceProvider<?>> provider;
public DsDbStoreChooserComp(Property<DataStore> input, ObservableValue<DataSourceProvider<?>> provider) {
this.input = input;
this.provider = provider;
}
@Override
protected Region createSimple() {
var filter = Bindings.createObjectBinding(
() -> (Predicate<DataStoreEntry>) e -> {
if (provider.getValue() == null) {
return e.getProvider().getCategory() == DataStoreProvider.DataCategory.DATABASE;
}
return provider.getValue().couldSupportStore(e.getStore());
},
provider);
var connections = new TabPaneComp.Entry(
AppI18n.observable("savedConnections"),
"mdi2m-monitor",
NamedStoreChoiceComp.create(filter, input, DataStoreProvider.DataCategory.DATABASE)
.styleClass("store-local-file-chooser"));
var pane = new TabPaneComp(new SimpleObjectProperty<>(connections), List.of(connections));
pane.apply(s -> AppFont.normal(s.get()));
return pane.createRegion();
}
}

View file

@ -121,7 +121,7 @@ public class DsStreamStoreChoiceComp extends SimpleComp implements Validatable {
var other = new TabPaneComp.Entry(
AppI18n.observable("other"),
"mdrmz-web_asset",
new DataStoreSelectorComp(DataStoreProvider.DataCategory.STREAM, otherStore));
new DataStoreSelectorComp(DataStoreProvider.DisplayCategory.HOST, otherStore));
var selectedTab = new SimpleObjectProperty<TabPaneComp.Entry>();
if (localStore.get() != null) {

View file

@ -105,11 +105,11 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
v -> true,
newE -> {
ThreadHelper.runAsync(() -> {
e.applyChanges(newE);
if (!DataStorage.get().getStoreEntries().contains(e)) {
DataStorage.get().addStoreEntry(e);
} else {
DataStorage.get().updateEntry(e, newE);
}
DataStorage.get().refresh();
});
},
true);

View file

@ -146,7 +146,7 @@ public class NamedStoreChoiceComp extends SimpleComp implements Validatable {
var text = new LabelComp(AppI18n.observable("noMatchingStoreFound"))
.apply(struc -> VBox.setVgrow(struc.get(), Priority.ALWAYS));
var addButton = new ButtonComp(AppI18n.observable("addStore"), null, () -> {
GuiDsStoreCreator.showCreation(v -> v.getCategory().equals(category));
// GuiDsStoreCreator.showCreation(v -> v.getCategory().equals(category));
});
var notice = new VerticalComp(List.of(text, addButton))
.apply(struc -> {

View file

@ -195,6 +195,10 @@ public class AppI18n {
}
public String getMarkdownDocumentation(String name) {
if (!markdownDocumentations.containsKey(name)) {
TrackEvent.withWarn("Markdown documentation for key " + name + " not found").handle();
}
return markdownDocumentations.getOrDefault(name, "");
}

View file

@ -1,6 +1,7 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.core.*;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.ErrorHandler;
import io.xpipe.app.issue.TrackEvent;
@ -105,6 +106,11 @@ public abstract class OperationMode {
setup(args);
LauncherCommand.runLauncher(usedArgs);
inStartup = false;
postInit(args);
}
public static void postInit(String[] args) {
DataStoreProviders.postInit(AppExtensionManager.getInstance().getExtendedLayer());
}
public static boolean isInStartup() {

View file

@ -15,12 +15,12 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
@Override
public Response handleRequest(BeaconHandler handler, Request msg) {
var categories = DataStoreProvider.DataCategory.values();
var categories = DataStoreProvider.DisplayCategory.values();
var all = DataStoreProviders.getAll();
var map = Arrays.stream(categories)
.collect(Collectors.toMap(category -> getName(category), category -> all.stream()
.filter(dataStoreProvider ->
dataStoreProvider.getCategory().equals(category))
dataStoreProvider.getDisplayCategory().equals(category))
.map(p -> ProviderEntry.builder()
.id(p.getId())
.description(p.getDisplayDescription())
@ -31,7 +31,7 @@ public class StoreProviderListExchangeImpl extends StoreProviderListExchange
return Response.builder().entries(map).build();
}
private String getName(DataStoreProvider.DataCategory category) {
private String getName(DataStoreProvider.DisplayCategory category) {
return category.name().substring(0, 1).toUpperCase()
+ category.name().substring(1).toLowerCase();
}

View file

@ -25,7 +25,6 @@ public interface DataStoreProvider {
}
default void validate() {
getCategory();
for (Class<?> storeClass : getStoreClasses()) {
if (!JacksonizedValue.class.isAssignableFrom(storeClass)) {
throw new ExtensionException(
@ -34,6 +33,8 @@ public interface DataStoreProvider {
}
}
default void preAdd(DataStore store) {}
default Comp<?> customDisplay(StoreSection s) {
return new StandardStoreEntryComp(s.getWrapper(), null);
}
@ -90,27 +91,7 @@ public interface DataStoreProvider {
return null;
}
default DataCategory getCategory() {
var c = getStoreClasses().get(0);
if (StreamDataStore.class.isAssignableFrom(c)) {
return DataCategory.STREAM;
}
if (FileSystem.class.isAssignableFrom(c) || ShellStore.class.isAssignableFrom(c)) {
return DataCategory.SHELL;
}
throw new ExtensionException("Provider " + getId() + " has no set category");
}
default DisplayCategory getDisplayCategory() {
var category = getCategory();
if (category.equals(DataCategory.SHELL)) {
return DisplayCategory.HOST;
}
if (category.equals(DataCategory.DATABASE)) {
return DisplayCategory.DATABASE;
}
return DisplayCategory.OTHER;
}
@ -130,19 +111,26 @@ public interface DataStoreProvider {
return true;
}
default void postInit(){
}
default void storageInit() throws Exception {}
default boolean isShareable() {
return false;
}
String queryInformationString(DataStore store, int length) throws Exception;
default String queryInformationString(DataStore store, int length) throws Exception {
return null;
}
default String queryInvalidInformationString(DataStore store, int length) throws Exception {
return null;
}
String toSummaryString(DataStore store, int length);
default String toSummaryString(DataStore store, int length) {
return null;
}
default String i18n(String key) {
return AppI18n.get(getId() + "." + key);
@ -205,6 +193,7 @@ public interface DataStoreProvider {
DATABASE,
SHELL,
COMMAND,
TUNNEL,
OTHER
}
}

View file

@ -17,7 +17,6 @@ public class DataStoreProviders {
if (ALL == null) {
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
.map(ServiceLoader.Provider::get)
.sorted(Comparator.comparing(DataStoreProvider::getId))
.collect(Collectors.toList());
ALL.removeIf(p -> {
try {
@ -35,6 +34,20 @@ public class DataStoreProviders {
}
}
public static void postInit(ModuleLayer layer) {
ALL = ServiceLoader.load(layer, DataStoreProvider.class).stream()
.map(ServiceLoader.Provider::get)
.sorted(Comparator.comparing(DataStoreProvider::getId))
.collect(Collectors.toList());
ALL.forEach(p -> {
try {
p.postInit();
} catch (Throwable e) {
ErrorEvent.fromThrowable(e).handle();
}
});
}
public static Optional<DataStoreProvider> byName(String name) {
if (ALL == null) {
throw new IllegalStateException("Not initialized");

View file

@ -42,6 +42,11 @@ public class DataStateProviderImpl extends DataStateProvider {
return c.cast(result);
}
public boolean isInStorage(DataStore store) {
var entry = DataStorage.get().getStoreEntryIfPresent(store);
return entry.isPresent();
}
@Override
public Path getInternalStreamStore(UUID id) {
return DataStorage.get().getInternalStreamPath(id);

View file

@ -92,6 +92,7 @@ public abstract class DataStorage {
Collections.reverse(ordered);
synchronized (this) {
ordered.forEach(entry -> entry.finalizeEntry());
this.storeEntries.removeAll(ordered);
this.listeners.forEach(l -> l.onStoreRemove(ordered.toArray(DataStoreEntry[]::new)));
}
@ -213,6 +214,12 @@ public abstract class DataStorage {
}
}
public void updateEntry(DataStoreEntry entry, DataStoreEntry newEntry) {
newEntry.finalizeEntry();
entry.applyChanges(newEntry);
entry.initializeEntry();
}
public void refreshAsync(DataStoreEntry element, boolean deep) {
ThreadHelper.runAsync(() -> {
try {
@ -233,6 +240,8 @@ public abstract class DataStorage {
}
public void addStoreEntry(@NonNull DataStoreEntry e) {
e.getProvider().preAdd(e.getStore());
synchronized (this) {
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
@ -241,16 +250,22 @@ public abstract class DataStorage {
save();
this.listeners.forEach(l -> l.onStoreAdd(e));
e.initializeEntry();
}
public void addStoreEntries(@NonNull DataStoreEntry... es) {
synchronized (this) {
for (DataStoreEntry e : es) {
e.getProvider().preAdd(e.getStore());
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
propagateUpdate(e);
}
this.listeners.forEach(l -> l.onStoreAdd(es));
for (DataStoreEntry e : es) {
e.initializeEntry();
}
}
save();
}
@ -284,6 +299,7 @@ public abstract class DataStorage {
}
public void deleteStoreEntry(@NonNull DataStoreEntry store) {
store.finalizeEntry();
synchronized (this) {
this.storeEntries.remove(store);
}
@ -298,13 +314,6 @@ public abstract class DataStorage {
public abstract void load();
public void refresh() {
getStoreEntries().forEach(entry -> {
entry.simpleRefresh();
});
save();
}
public abstract void save();
public synchronized Optional<DataStoreEntry> getStoreEntry(UUID id) {

View file

@ -288,6 +288,36 @@ public class DataStoreEntry extends StorageElement {
}
}
@SneakyThrows
public void initializeEntry() {
try {
state = State.VALIDATING;
listeners.forEach(l -> l.onUpdate());
store.initializeValidate();
state = State.COMPLETE_AND_VALID;
} catch (Exception e) {
state = State.COMPLETE_BUT_INVALID;
ErrorEvent.fromThrowable(e).handle();
} finally {
propagateUpdate();
}
}
@SneakyThrows
public void finalizeEntry() {
try {
state = State.VALIDATING;
listeners.forEach(l -> l.onUpdate());
store.finalizeValidate();
state = State.COMPLETE_AND_VALID;
} catch (Exception e) {
state = State.COMPLETE_BUT_INVALID;
ErrorEvent.fromThrowable(e).handle();
} finally {
propagateUpdate();
}
}
@Override
protected boolean shouldSave() {
return getStore() == null || getStore().shouldSave();

View file

@ -42,6 +42,12 @@ public class OptionsBuilder {
entries.add(entry);
}
public OptionsBuilder sub(OptionsBuilder builder) {
props.addAll(builder.props);
pushComp(builder.buildComp());
return this;
}
public OptionsBuilder addTitle(String titleKey) {
finishCurrent();
entries.add(new OptionsComp.Entry(

View file

@ -42,6 +42,7 @@ clean=Clean
refresh=Refresh
remove=Remove
addDatabase=Add Database ...
addTunnel=Add Tunnel ...
addHost=Add Remote Host ...
addShell=Add Environment ...
addCommand=Add Command ...

View file

@ -116,7 +116,6 @@ noMatchingSourceFound=No matching source found
addSource=Add Source
edit=Edit
addStream=Add File
addDatabase=Add Database
pipeStream=Pipe File
pipeDatabase=Pipe Database
transfer=Transfer

View file

@ -9,13 +9,10 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
@JsonTypeName("internalStream")
@ -35,20 +32,10 @@ public class InternalStreamStore extends JacksonizedValue implements StreamDataS
return DataFlow.INPUT_OUTPUT;
}
@Override
public Optional<String> determineDefaultName() {
return Optional.of(uuid.toString());
}
private Path getFile() {
return DataStateProvider.get().getInternalStreamStore(uuid);
}
@Override
public Optional<Instant> determineLastModified() throws IOException {
return Optional.of(Files.getLastModifiedTime(getFile()).toInstant());
}
@Override
public InputStream openInput() throws Exception {
return Files.newInputStream(getFile());

View file

@ -5,11 +5,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.store.DataStore;
import lombok.EqualsAndHashCode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Optional;
@JsonTypeName("localDir")
@EqualsAndHashCode
@ -22,21 +18,6 @@ public class LocalDirectoryDataStore implements DataStore {
this.file = file;
}
@Override
public Optional<String> determineDefaultName() {
return Optional.of(file.getFileName().toString());
}
@Override
public Optional<Instant> determineLastModified() {
try {
var l = Files.getLastModifiedTime(file);
return Optional.of(l.toInstant());
} catch (IOException e) {
return Optional.empty();
}
}
public Path getPath() {
return file;
}

View file

@ -6,9 +6,6 @@ import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.time.Instant;
import java.util.Optional;
/**
* A store that refers to another store in the XPipe storage.
* The referenced store has to be resolved by the caller manually, as this class does not act as a resolver.
@ -36,13 +33,4 @@ public final class NamedStore implements DataStore {
throw new UnsupportedOperationException();
}
@Override
public Optional<String> determineDefaultName() {
throw new UnsupportedOperationException();
}
@Override
public Optional<Instant> determineLastModified() {
throw new UnsupportedOperationException();
}
}

View file

@ -5,10 +5,6 @@ import io.xpipe.core.impl.StdinDataStore;
import io.xpipe.core.impl.StdoutDataStore;
import io.xpipe.core.source.DataSource;
import java.io.IOException;
import java.time.Instant;
import java.util.Optional;
/**
* A data store represents some form of a location where data is stored, e.g. a file or a database.
* It does not contain any information on what data is stored,
@ -77,6 +73,10 @@ public interface DataStore {
*/
default void validate() throws Exception {}
default void initializeValidate() throws Exception {}
default void finalizeValidate() throws Exception {}
default void checkComplete() throws Exception {}
default boolean delete() {
@ -90,19 +90,4 @@ public interface DataStore {
default <DS extends DataStore> DS asNeeded() {
return (DS) this;
}
/**
* Determines on optional default name for this data store that is
* used when determining a suitable default name for a data source.
*/
default Optional<String> determineDefaultName() {
return Optional.empty();
}
/**
* Determines the last modified of this data store if this data store supports it.
*/
default Optional<Instant> determineLastModified() throws IOException {
return Optional.empty();
}
}

View file

@ -1,20 +1,11 @@
package io.xpipe.core.store;
import java.util.Optional;
/**
* Represents a store that has a filename.
* Note that this does not only apply to file stores but any other store as well that has some kind of file name.
*/
public interface FilenameStore extends DataStore {
@Override
default Optional<String> determineDefaultName() {
var n = getFileName();
var i = n.lastIndexOf('.');
return Optional.of(i != -1 ? n.substring(0, i) : n);
}
default String getFileExtension() {
var split = getFileName().split("[\\\\.]");
if (split.length == 0) {

View file

@ -6,6 +6,10 @@ import java.util.function.Supplier;
public interface StatefulDataStore extends DataStore {
default boolean isInStorage() {
return DataStateProvider.get().isInStorage(this);
}
default <T> T getState(String key, Class<T> c, T def) {
return DataStateProvider.get().getState(this, key, c, () -> def);
}

View file

@ -25,5 +25,7 @@ public abstract class DataStateProvider {
public abstract <T> T getState(DataStore store, String key, Class<T> c, Supplier<T> def);
public abstract boolean isInStorage(DataStore store);
public abstract Path getInternalStreamStore(UUID id);
}

View file

@ -25,7 +25,6 @@ import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
@JsonTypeName("http")
@SuperBuilder
@ -101,11 +100,6 @@ public class HttpStore extends JacksonizedValue implements StreamDataStore, Stat
Validators.nonNull(headers, "Headers");
}
@Override
public Optional<String> determineDefaultName() {
return Optional.ofNullable(getURL().getHost());
}
@Override
public void validate() throws Exception {
var client = createClient();

View file

@ -70,11 +70,6 @@ public class InMemoryStoreProvider implements DataStoreProvider {
});
}
@Override
public DataCategory getCategory() {
return DataCategory.STREAM;
}
@Override
public DataStore defaultStore() {
return new InMemoryStore(new byte[0]);

View file

@ -1 +1 @@
1.3.2
1.4.0