This commit is contained in:
crschnick 2023-02-08 14:34:32 +00:00
parent 8b4a59e8f0
commit 84d0a70ec8
61 changed files with 324 additions and 382 deletions

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.about;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppLogs;
import io.xpipe.app.editor.EditorState;
import io.xpipe.app.util.ExternalEditor;
import io.xpipe.app.issue.UserReportComp;
import io.xpipe.core.util.XPipeInstallation;
import io.xpipe.extension.I18n;
@ -30,7 +30,7 @@ public class BrowseDirectoryComp extends SimpleComp {
.addComp(
"logFile",
new ButtonComp(I18n.observable("openCurrentLogFile"), () -> {
EditorState.get().openInEditor(AppLogs.get().getSessionLogsDirectory().resolve("xpipe.log").toString());
ExternalEditor.get().openInEditor(AppLogs.get().getSessionLogsDirectory().resolve("xpipe.log").toString());
}),
null)
.addComp(

View file

@ -2,7 +2,7 @@ package io.xpipe.app.comp.about;
import io.xpipe.extension.util.XPipeDistributionType;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.grid.AppUpdater;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.extension.I18n;
import io.xpipe.extension.fxcomps.SimpleComp;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.editor.EditorState;
import io.xpipe.app.util.ExternalEditor;
import io.xpipe.extension.fxcomps.Comp;
import io.xpipe.extension.fxcomps.SimpleComp;
import io.xpipe.extension.fxcomps.impl.IconButtonComp;
@ -45,7 +45,7 @@ public class IntegratedTextAreaComp extends SimpleComp {
}
private Region createOpenButton(Region container) {
var button = new IconButtonComp("mdal-edit", () -> EditorState.get()
var button = new IconButtonComp("mdal-edit", () -> ExternalEditor.get()
.startEditing(identifier, fileType, this, value.getValue(), (s) -> {
Platform.runLater(() -> value.setValue(s));
})).createRegion();

View file

@ -3,7 +3,6 @@ package io.xpipe.app.comp.source;
import io.xpipe.app.comp.base.MultiStepComp;
import io.xpipe.app.comp.source.store.DsDbStoreChooserComp;
import io.xpipe.app.comp.source.store.DsStreamStoreChoiceComp;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import io.xpipe.extension.DataSourceProvider;
@ -42,7 +41,7 @@ public class GuiDsStoreSelectStep extends MultiStepComp.Step<CompStructure<? ext
DataSourceProvider.Category category,
ObjectProperty<? extends DataSource<?>> baseSource,
BooleanProperty loading) {
super(Hyperlinks.openLink(Hyperlinks.DOCS_DATA_INPUT));
super(null);
this.parent = parent;
this.provider = provider;
this.input = input;

View file

@ -33,12 +33,15 @@ public class SourceEntryContextMenu<S extends CompStructure<?>> extends PopupMen
AppFont.normal(cm.getStyleableNode());
for (var actionProvider : entry.getActionProviders()) {
var name = actionProvider.getName(entry.getEntry().getSource().asNeeded());
var icon = actionProvider.getIcon(entry.getEntry().getSource().asNeeded());
var c = actionProvider.getDataSourceCallSite();
var name = c.getName(entry.getEntry().getSource().asNeeded());
var icon = c.getIcon(entry.getEntry().getSource().asNeeded());
var item = new MenuItem(null, new FontIcon(icon));
item.setOnAction(event -> {
event.consume();
try {
actionProvider.execute(entry.getEntry().getSource().asNeeded());
var action = c.createAction(entry.getEntry().getSource().asNeeded());
action.execute();
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}

View file

@ -4,16 +4,17 @@ import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
import io.xpipe.app.comp.storage.StorageFilter;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.collection.SourceCollectionWrapper;
import io.xpipe.app.storage.*;
import io.xpipe.app.storage.DataSourceEntry;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.StorageElement;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataFlow;
import io.xpipe.extension.DataSourceActionProvider;
import io.xpipe.extension.DataStoreProviders;
import io.xpipe.extension.I18n;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.fxcomps.util.PlatformThread;
import io.xpipe.extension.util.ActionProvider;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import lombok.Value;
import java.time.Instant;
@ -29,13 +30,10 @@ public class SourceEntryWrapper implements StorageFilter.Filterable {
StringProperty information = new SimpleStringProperty();
StringProperty storeSummary = new SimpleStringProperty();
Property<Instant> lastUsed = new SimpleObjectProperty<>();
Property<AccessMode> accessMode = new SimpleObjectProperty<>();
Property<DataFlow> dataFlow = new SimpleObjectProperty<>();
ObjectProperty<DataSourceEntry.State> state = new SimpleObjectProperty<>();
BooleanProperty loading = new SimpleBooleanProperty();
List<DataSourceActionProvider<?>> actionProviders = new ArrayList<>();
ListProperty<ApplicationAccess> accesses = new SimpleListProperty<>(FXCollections.observableArrayList());
List<ActionProvider> actionProviders = new ArrayList<>();
public SourceEntryWrapper(DataSourceEntry entry) {
this.entry = entry;
@ -100,16 +98,21 @@ public class SourceEntryWrapper implements StorageFilter.Filterable {
loading.setValue(entry.getState() == null || entry.getState() == DataSourceEntry.State.VALIDATING);
actionProviders.clear();
actionProviders.addAll(DataSourceActionProvider.ALL.stream()
actionProviders.addAll(ActionProvider.ALL.stream()
.filter(p -> {
try {
if (!entry.getState().isUsable()) {
return false;
}
return p.getApplicableClass()
var c = p.getDataSourceCallSite();
if (c == null) {
return false;
}
return c.getApplicableClass()
.isAssignableFrom(entry.getSource().getClass())
&& p.isApplicable(entry.getSource().asNeeded());
&& c.isApplicable(entry.getSource().asNeeded());
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
return false;

View file

@ -45,9 +45,9 @@ public class SourceStorageEmptyIntroComp extends SimpleComp {
documentation.heightProperty().addListener((c, o, n) -> {
dfi.iconSizeProperty().set(n.intValue());
});
var docLink = new Hyperlink(Hyperlinks.DOCS_GETTING_STARTED);
var docLink = new Hyperlink(Hyperlinks.DOCUMENTATION);
docLink.setOnAction(e -> {
Hyperlinks.open(Hyperlinks.DOCS_GETTING_STARTED);
Hyperlinks.open(Hyperlinks.DOCUMENTATION);
});
var docLinkPane = new StackPane(docLink);
docLinkPane.setAlignment(Pos.CENTER);

View file

@ -51,9 +51,9 @@ public class StoreStorageEmptyIntroComp extends SimpleComp {
documentation.heightProperty().addListener((c, o, n) -> {
dofi.iconSizeProperty().set(n.intValue());
});
var docLink = new Hyperlink(Hyperlinks.DOCS_GETTING_STARTED);
var docLink = new Hyperlink(Hyperlinks.DOCUMENTATION);
docLink.setOnAction(e -> {
Hyperlinks.open(Hyperlinks.DOCS_GETTING_STARTED);
Hyperlinks.open(Hyperlinks.DOCUMENTATION);
});
var docLinkPane = new StackPane(docLink);
docLinkPane.setAlignment(Pos.CENTER);

View file

@ -1,6 +1,6 @@
package io.xpipe.app.core;
import io.xpipe.app.util.ConfigHelper;
import io.xpipe.app.util.JsonConfigHelper;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.Cache;
import io.xpipe.extension.event.ErrorEvent;
@ -49,7 +49,7 @@ public class AppCache implements Cache {
var path = getPath(key);
if (Files.exists(path)) {
try {
var tree = ConfigHelper.readConfig(path);
var tree = JsonConfigHelper.readConfig(path);
if (tree.isMissingNode()) {
return notPresent.get();
}
@ -69,7 +69,7 @@ public class AppCache implements Cache {
try {
FileUtils.forceMkdirParent(path.toFile());
var tree = JacksonMapper.newMapper().valueToTree(val);
ConfigHelper.writeConfig(path, tree);
JsonConfigHelper.writeConfig(path, tree);
} catch (Exception e) {
ErrorEvent.fromThrowable("Could not parse cached data for key " + key, e)
.omitted(true)

View file

@ -4,11 +4,16 @@ import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.SupportedLocale;
import io.xpipe.app.util.ModuleHelper;
import io.xpipe.extension.I18n;
import io.xpipe.extension.Translatable;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.value.ObservableValue;
import lombok.SneakyThrows;
import org.apache.commons.io.FilenameUtils;
import org.ocpsoft.prettytime.PrettyTime;
@ -89,11 +94,31 @@ public class AppI18n implements I18n {
prettyTime = null;
}
@SneakyThrows
private static String getCallerModuleName() {
var callers = ModuleHelper.CallingClass.INSTANCE.getCallingClasses();
for (Class<?> caller : callers) {
if (caller.equals(ModuleHelper.CallingClass.class)
|| caller.equals(ModuleHelper.class)
|| caller.equals(AppI18n.class)
|| caller.equals(I18n.class)
|| caller.equals(FancyTooltipAugment.class)
|| caller.equals(PrefsChoiceValue.class)
|| caller.equals(Translatable.class)
|| caller.equals(DynamicOptionsBuilder.class)) {
continue;
}
var split = caller.getModule().getName().split("\\.");
return split[split.length - 1];
}
return "";
}
@Override
public String getKey(String s) {
var key = s;
if (!s.contains(".")) {
key = ModuleHelper.getCallerModuleName() + "." + s;
key = getCallerModuleName() + "." + s;
}
return key;
}

View file

@ -3,8 +3,8 @@ package io.xpipe.app.core.mode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.editor.EditorState;
import io.xpipe.app.grid.AppUpdater;
import io.xpipe.app.util.ExternalEditor;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.app.issue.BasicErrorHandler;
import io.xpipe.app.issue.ErrorHandler;
import io.xpipe.app.prefs.AppPrefs;
@ -40,7 +40,7 @@ public class BaseMode extends OperationMode {
AppCharsetter.init();
DataStorage.init();
FileWatchManager.init();
EditorState.init();
ExternalEditor.init();
AppSocketServer.init();
AppUpdater.init();
TrackEvent.info("mode", "Finished base components initialization");

View file

@ -1,6 +1,6 @@
package io.xpipe.app.core.mode;
import io.xpipe.app.grid.UpdateChangelogAlert;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.core.App;
import io.xpipe.app.core.AppGreetings;
import io.xpipe.app.issue.ErrorHandler;

View file

@ -3,7 +3,7 @@ package io.xpipe.app.core.mode;
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
import io.xpipe.app.comp.storage.store.StoreViewState;
import io.xpipe.app.core.*;
import io.xpipe.app.grid.UpdateAvailableAlert;
import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.util.ThreadHelper;
import javafx.application.Application;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.exchange.cli;
import io.xpipe.app.exchange.MessageExchangeImpl;
import io.xpipe.app.storage.XPipeInstanceHelper;
import io.xpipe.app.update.XPipeInstanceHelper;
import io.xpipe.beacon.BeaconHandler;
import io.xpipe.beacon.exchange.cli.InstanceExchange;
import io.xpipe.core.impl.LocalStore;

View file

@ -3,7 +3,7 @@ package io.xpipe.app.issue;
import io.sentry.Sentry;
import io.xpipe.app.core.*;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.grid.AppUpdater;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.extension.I18n;
import io.xpipe.extension.event.ErrorEvent;
import javafx.application.Platform;

View file

@ -16,6 +16,7 @@ import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.prefs.PrefsHandler;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.XPipeDistributionType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
@ -26,6 +27,24 @@ import java.util.*;
public class AppPrefs {
private static ObservableBooleanValue bindDeveloperTrue(ObservableBooleanValue o) {
return Bindings.createBooleanBinding(
() -> {
return AppPrefs.get().developerMode().getValue() || o.get();
},
o,
AppPrefs.get().developerMode());
}
private static ObservableBooleanValue bindDeveloperFalse(ObservableBooleanValue o) {
return Bindings.createBooleanBinding(
() -> {
return !AppPrefs.get().developerMode().getValue() || o.get();
},
o,
AppPrefs.get().developerMode());
}
private static final int tooltipDelayMin = 0;
private static final int tooltipDelayMax = 1500;
private static final int fontSizeMin = 10;
@ -134,7 +153,7 @@ public class AppPrefs {
private final ObjectProperty<Path> effectiveStorageDirectory = STORAGE_DIR_FIXED
? new SimpleObjectProperty<>(AppProperties.get().getDataDir().resolve("storage"))
: internalStorageDirectory;
private final StringField storageDirectoryControl = Fields.ofPath(effectiveStorageDirectory)
private final StringField storageDirectoryControl = PrefFields.ofPath(effectiveStorageDirectory)
.editable(!STORAGE_DIR_FIXED)
.validate(
CustomValidators.absolutePath(),
@ -230,24 +249,24 @@ public class AppPrefs {
return effectiveDeveloperMode;
}
public ReadOnlyBooleanProperty developerDisableUpdateVersionCheck() {
return developerDisableUpdateVersionCheck;
public ObservableBooleanValue developerDisableUpdateVersionCheck() {
return bindDeveloperTrue(developerDisableUpdateVersionCheck);
}
public ReadOnlyBooleanProperty developerDisableGuiRestrictions() {
return developerDisableGuiRestrictions;
public ObservableBooleanValue developerDisableGuiRestrictions() {
return bindDeveloperTrue(developerDisableGuiRestrictions);
}
public ReadOnlyBooleanProperty developerDisableConnectorInstallationVersionCheck() {
return developerDisableConnectorInstallationVersionCheck;
public ObservableBooleanValue developerDisableConnectorInstallationVersionCheck() {
return bindDeveloperTrue(developerDisableConnectorInstallationVersionCheck);
}
public ReadOnlyBooleanProperty developerShowHiddenProviders() {
return developerShowHiddenProviders;
public ObservableBooleanValue developerShowHiddenProviders() {
return bindDeveloperTrue(developerShowHiddenProviders);
}
public ReadOnlyBooleanProperty developerShowHiddenEntries() {
return developerShowHiddenEntries;
public ObservableBooleanValue developerShowHiddenEntries() {
return bindDeveloperTrue(developerShowHiddenEntries);
}
private AppPreferencesFx preferencesFx;

View file

@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.util.ConfigHelper;
import io.xpipe.app.util.JsonConfigHelper;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;
@ -32,7 +32,7 @@ public class JsonStorageHandler implements StorageHandler {
private JsonNode getContent(String key) {
if (content == null) {
content = (ObjectNode) ConfigHelper.readConfig(file);
content = (ObjectNode) JsonConfigHelper.readConfig(file);
}
return content.get(key);
}
@ -42,7 +42,7 @@ public class JsonStorageHandler implements StorageHandler {
}
void save() {
ConfigHelper.writeConfig(file, content);
JsonConfigHelper.writeConfig(file, content);
}
@Override

View file

@ -11,7 +11,7 @@ import javafx.util.StringConverter;
import java.nio.file.Path;
import java.util.Objects;
public class Fields {
public class PrefFields {
public static StringField ofPath(ObjectProperty<Path> fileProperty) {
StringProperty stringProperty = new SimpleStringProperty();

View file

@ -1,6 +0,0 @@
package io.xpipe.app.storage;
public enum AccessMode {
READ,
WRITE
}

View file

@ -1,6 +0,0 @@
package io.xpipe.app.storage;
import java.time.Instant;
import java.util.UUID;
public record ApplicationAccess(String name, UUID uuid, Instant start, AccessMode mode) {}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.grid;
package io.xpipe.app.update;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.extension.event.ErrorEvent;

View file

@ -1,10 +1,10 @@
package io.xpipe.app.grid;
package io.xpipe.app.update;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.app.util.TerminalProvider;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.impl.LocalProcessControlProvider;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellProcessControl;
@ -196,8 +196,7 @@ public class AppInstaller {
var command = "set -x\n" + "DEBIAN_FRONTEND=noninteractive sudo apt-get remove -qy xpipe\n"
+ "DEBIAN_FRONTEND=noninteractive sudo apt-get install -qy \"" + file + "\"\n"
+ "xpipe daemon start";
var script = ScriptHelper.createLocalExecScript(command);
LocalProcessControlProvider.get().openInTerminal("X-Pipe Updater", script);
TerminalProvider.open("X-Pipe Updater", command);
}
}
@ -223,8 +222,7 @@ public class AppInstaller {
@Override
public void installLocal(String file) throws Exception {
var command = "set -x\n" + "sudo rpm -U -v --force \"" + file + "\"\n" + "xpipe daemon start";
var script = ScriptHelper.createLocalExecScript(command);
LocalProcessControlProvider.get().openInTerminal("X-Pipe Updater", script);
TerminalProvider.open("X-Pipe Updater", command);
}
}
@ -250,8 +248,7 @@ public class AppInstaller {
@Override
public void installLocal(String file) throws Exception {
var command = "set -x\n" + "sudo installer -verboseR -allowUntrusted -pkg \"" + file + "\" -target /\n" + "xpipe daemon start";
var script = ScriptHelper.createLocalExecScript(command);
LocalProcessControlProvider.get().openInTerminal("X-Pipe Updater", script);
TerminalProvider.open("X-Pipe Updater", command);
}
}
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.grid;
package io.xpipe.app.update;
import io.xpipe.app.core.AppCache;
import io.xpipe.extension.util.XPipeDistributionType;
@ -6,7 +6,7 @@ import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.impl.LocalProcessControlProvider;
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.extension.event.ErrorEvent;
import io.xpipe.extension.event.TrackEvent;
@ -112,7 +112,7 @@ public class AppUpdater {
if (layer == null) {
return;
}
LocalProcessControlProvider.init(layer);
ProcessControlProvider.init(layer);
INSTANCE = new AppUpdater();
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.grid;
package io.xpipe.app.update;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.extension.I18n;

View file

@ -1,4 +1,4 @@
package io.xpipe.app.grid;
package io.xpipe.app.update;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.AppWindowHelper;

View file

@ -1,6 +1,7 @@
package io.xpipe.app.storage;
package io.xpipe.app.update;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.beacon.XPipeInstance;
import io.xpipe.core.store.ShellStore;
import io.xpipe.extension.event.ErrorEvent;

View file

@ -1,29 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.core.util.SecretValue;
import io.xpipe.extension.I18n;
import javafx.scene.control.Alert;
import javafx.scene.control.PasswordField;
import java.util.concurrent.atomic.AtomicReference;
public class AskpassAlert {
public static SecretValue query() {
AtomicReference<SecretValue> password = new AtomicReference<>();
var result = AppWindowHelper.showBlockingAlert(alert -> {
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.setTitle(I18n.get("providePassword"));
alert.setHeaderText(I18n.get("queryPasswordDescription"));
var textField = new PasswordField();
textField.textProperty().addListener((c, o, n) -> {
password.set(new SecretValue(n));
});
alert.getDialogPane().setContent(textField);
})
.filter(buttonType -> buttonType.getButtonData().isDefaultButton());
return result.isPresent() ? password.get() : null;
}
}

View file

@ -1,26 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.prefs.AppPrefs;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableBooleanValue;
public class DeveloperHelper {
public static ObservableBooleanValue bindTrue(ObservableBooleanValue o) {
return Bindings.createBooleanBinding(
() -> {
return AppPrefs.get().developerMode().getValue() || o.get();
},
o,
AppPrefs.get().developerMode());
}
public static ObservableBooleanValue bindFalls(ObservableBooleanValue o) {
return Bindings.createBooleanBinding(
() -> {
return !AppPrefs.get().developerMode().getValue() || o.get();
},
o,
AppPrefs.get().developerMode());
}
}

View file

@ -1,4 +1,4 @@
package io.xpipe.app.editor;
package io.xpipe.app.util;
import io.xpipe.app.core.FileWatchManager;
import io.xpipe.app.prefs.AppPrefs;
@ -22,14 +22,14 @@ import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Consumer;
public class EditorState {
public class ExternalEditor {
private static final Path TEMP =
FileUtils.getTempDirectory().toPath().resolve("xpipe").resolve("editor");
private static EditorState INSTANCE;
private static ExternalEditor INSTANCE;
private final Set<Entry> openEntries = new CopyOnWriteArraySet<>();
public static EditorState get() {
public static ExternalEditor get() {
return INSTANCE;
}
@ -42,7 +42,7 @@ public class EditorState {
}
public static void init() {
INSTANCE = new EditorState();
INSTANCE = new ExternalEditor();
try {
FileUtils.forceMkdir(TEMP.toFile());

View file

@ -1,66 +0,0 @@
package io.xpipe.app.util;
import com.vladsch.flexmark.util.sequence.Html5Entities;
import io.xpipe.modulefs.ModuleFileSystem;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
public class FlexmarkHelper {
public static void loadHtmlEscapes() {
Class<?> c = null;
try {
c = Html5Entities.class;
Html5Entities.entityToString(null);
} catch (Throwable ignored) {
}
try {
var field = c.getDeclaredField("NAMED_CHARACTER_REFERENCES");
field.setAccessible(true);
field.setInt(field, field.getModifiers() & ~Modifier.FINAL);
try (var fs = ModuleFileSystem.create("module:/com.vladsch.flexmark_util_html")) {
var file = fs.getPath("com/vladsch/flexmark/util/html/entities.properties");
try (var in = Files.newInputStream(file)) {
var r = readEntities(in);
field.set(null, r);
}
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
private static Map<String, String> readEntities(InputStream stream) {
Map<String, String> entities = new HashMap<>();
Charset charset = StandardCharsets.UTF_8;
try {
String line;
InputStreamReader streamReader = new InputStreamReader(stream, charset);
BufferedReader bufferedReader = new BufferedReader(streamReader);
while ((line = bufferedReader.readLine()) != null) {
if (line.length() == 0) {
continue;
}
int equal = line.indexOf("=");
String key = line.substring(0, equal);
String value = line.substring(equal + 1);
entities.put(key, value);
}
} catch (IOException e) {
throw new IllegalStateException("Failed reading data for HTML named character references", e);
}
entities.put("NewLine", "\n");
return entities;
}
}

View file

@ -9,17 +9,12 @@ import java.net.URI;
public class Hyperlinks {
public static final String WEBSITE = "https://xpipe.io";
public static final String DOCUMENTATION = "https://docs.xpipe.io";
public static final String DOCUMENTATION = "https://xpipe.io/docs";
public static final String GITHUB = "https://github.com/xpipe-io";
public static final String DISCORD = "https://discord.gg/8y89vS8cRb";
public static final String SLACK =
"https://join.slack.com/t/x-pipe/shared_invite/zt-1awjq0t5j-5i4UjNJfNe1VN4b_auu6Cg";
public static final String GUIDE = "https://github.com/crschnick/pdx_unlimiter/wiki/User-Guide";
public static final String DOCS_DATA_INPUT = "https://docs.xpipe.io/data-source-creation/data-input";
public static final String DOCS_BASE = "https://docs.xpipe.io/";
public static final String DOCS_GETTING_STARTED = "https://docs.xpipe.io/en/latest/index.html";
public static final String DOCS_PRIVACY = "https://docs.xpipe.io/en/latest/privacy.html";
public static final String DOCS_PRIVACY = "https://xpipe.io/docs/privacy";
public static Runnable openLink(String s) {
return () -> open(s);

View file

@ -15,13 +15,13 @@ import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
public class ConfigHelper {
public class JsonConfigHelper {
public static JsonNode readConfig(Path in) {
JsonNode node = JsonNodeFactory.instance.objectNode();
try {
if (Files.exists(in)) {
ObjectMapper o = JacksonMapper.newMapper();
ObjectMapper o = JacksonMapper.getDefault();
node = o.readTree(Files.readAllBytes(in));
}
} catch (IOException e) {
@ -41,7 +41,7 @@ public class ConfigHelper {
var writer = new StringWriter();
JsonFactory f = new JsonFactory();
try (JsonGenerator g = f.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
JacksonMapper.newMapper().writeTree(g, node);
JacksonMapper.getDefault().writeTree(g, node);
var newContent = writer.toString();
Files.writeString(out, newContent);
} catch (IOException e) {

View file

@ -1,11 +1,5 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.extension.I18n;
import io.xpipe.extension.Translatable;
import io.xpipe.extension.fxcomps.impl.FancyTooltipAugment;
import io.xpipe.extension.prefs.PrefsChoiceValue;
import io.xpipe.extension.util.DynamicOptionsBuilder;
import lombok.SneakyThrows;
import java.lang.reflect.Field;
@ -13,26 +7,6 @@ import java.lang.reflect.Method;
public class ModuleHelper {
@SneakyThrows
public static String getCallerModuleName() {
var callers = CallingClass.INSTANCE.getCallingClasses();
for (Class<?> caller : callers) {
if (caller.equals(CallingClass.class)
|| caller.equals(ModuleHelper.class)
|| caller.equals(AppI18n.class)
|| caller.equals(I18n.class)
|| caller.equals(FancyTooltipAugment.class)
|| caller.equals(PrefsChoiceValue.class)
|| caller.equals(Translatable.class)
|| caller.equals(DynamicOptionsBuilder.class)) {
continue;
}
var split = caller.getModule().getName().split("\\.");
return split[split.length - 1];
}
return "";
}
public static boolean isImage() {
return ModuleHelper.class
.getProtectionDomain()

View file

@ -2,8 +2,8 @@ package io.xpipe.app.util;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.grid.AppDownloads;
import io.xpipe.app.grid.AppInstaller;
import io.xpipe.app.update.AppDownloads;
import io.xpipe.app.update.AppInstaller;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.impl.FileNames;
import io.xpipe.core.process.ShellProcessControl;

View file

@ -0,0 +1,39 @@
package io.xpipe.app.util;
import io.xpipe.extension.util.ModuleLayerLoader;
import io.xpipe.extension.util.ScriptHelper;
import java.util.ServiceLoader;
public abstract class TerminalProvider {
private static TerminalProvider INSTANCE;
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
ServiceLoader.load(layer, TerminalProvider.class).findFirst().orElseThrow();
}
@Override
public boolean requiresFullDaemon() {
return true;
}
@Override
public boolean prioritizeLoading() {
return false;
}
}
public static void open(String title, String command) throws Exception {
if (command.contains("\n")) {
command = ScriptHelper.createLocalExecScript(command);
}
INSTANCE.openInTerminal(title, command);
}
protected abstract void openInTerminal(String title, String command) throws Exception;
}

View file

@ -7,7 +7,7 @@ import io.xpipe.app.comp.source.store.NamedStoreChoiceComp;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.grid.AppDownloads;
import io.xpipe.app.update.AppDownloads;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.charsetter.Charsetter;

View file

@ -27,8 +27,7 @@ open module io.xpipe.app {
exports io.xpipe.app.prefs;
exports io.xpipe.app.comp.source.store;
exports io.xpipe.app.storage;
exports io.xpipe.app.editor;
exports io.xpipe.app.grid;
exports io.xpipe.app.update;
exports io.xpipe.app.comp.storage;
exports io.xpipe.app.comp.storage.collection;
@ -98,6 +97,7 @@ open module io.xpipe.app {
requires jdk.jdwp.agent;
uses MessageExchangeImpl;
uses io.xpipe.app.util.TerminalProvider;
provides DataStateProvider with
DataStateProviderImpl;

View file

@ -3,7 +3,6 @@ package io.xpipe.cli;
import io.xpipe.beacon.exchange.MessageExchanges;
import io.xpipe.cli.util.CliProperties;
import io.xpipe.cli.util.PrettyTimeHelper;
import io.xpipe.core.impl.LocalProcessControlProvider;
import io.xpipe.core.util.JacksonMapper;
public class BuildTimeInitialization {
@ -13,7 +12,6 @@ public class BuildTimeInitialization {
CliProperties.init();
JacksonMapper.initClassBased();
MessageExchanges.loadAll();
LocalProcessControlProvider.init(null);
PrettyTimeHelper.init();
// System.out.println("Ending build time initialization");
}

View file

@ -1,6 +1,6 @@
package io.xpipe.cli.test;
import io.xpipe.extension.util.DaemonExtensionTest;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.api.Test;
import java.io.IOException;

View file

@ -1,6 +1,6 @@
package io.xpipe.cli.test;
import io.xpipe.extension.util.DaemonExtensionTest;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

View file

@ -1,6 +1,6 @@
package io.xpipe.cli.test;
import io.xpipe.extension.util.DaemonExtensionTest;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

View file

@ -1,6 +1,6 @@
package io.xpipe.cli.test;
import io.xpipe.extension.util.ExtensionTest;
import io.xpipe.extension.test.ExtensionTest;
import lombok.Getter;
import java.nio.file.Path;

View file

@ -1,40 +0,0 @@
package io.xpipe.core.impl;
import io.xpipe.core.process.ShellProcessControl;
import java.util.ServiceLoader;
public abstract class LocalProcessControlProvider {
private static LocalProcessControlProvider INSTANCE;
public static LocalProcessControlProvider get() {
if (INSTANCE == null) {
throw new IllegalStateException("Process control not initialized");
}
return INSTANCE;
}
public static void init(ModuleLayer layer) {
INSTANCE = layer != null
? ServiceLoader.load(layer, LocalProcessControlProvider.class)
.findFirst()
.orElse(null)
: ServiceLoader.load(LocalProcessControlProvider.class)
.findFirst()
.orElse(null);
}
public static ShellProcessControl create() {
if (INSTANCE == null) {
throw new IllegalStateException("Not initialized");
}
return INSTANCE.createProcessControl();
}
public abstract ShellProcessControl createProcessControl();
public abstract void openInTerminal(String title, String command) throws Exception;
}

View file

@ -48,7 +48,7 @@ public class LocalStore extends JacksonizedValue implements FileSystemStore, Mac
@Override
public ShellProcessControl create() {
return LocalProcessControlProvider.create();
return ProcessControlProvider.createLocal();
}
}

View file

@ -0,0 +1,50 @@
package io.xpipe.core.impl;
import io.xpipe.core.process.CommandProcessControl;
import io.xpipe.core.process.ShellProcessControl;
import lombok.NonNull;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.BiFunction;
import java.util.function.Function;
public abstract class ProcessControlProvider {
private static List<ProcessControlProvider> INSTANCES;
public static void init(ModuleLayer layer) {
INSTANCES = ServiceLoader.load(layer, ProcessControlProvider.class)
.stream().map(localProcessControlProviderProvider -> localProcessControlProviderProvider.get()).toList();
}
public static ShellProcessControl createLocal() {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.createLocalProcessControl()).findFirst().orElseThrow();
}
public static ShellProcessControl createSub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.sub(parent, commandFunction, terminalCommand)).findFirst().orElseThrow();
}
public static CommandProcessControl createCommand(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand) {
return INSTANCES.stream().map(localProcessControlProvider -> localProcessControlProvider.command(parent, command, terminalCommand)).findFirst().orElseThrow();
}
public abstract ShellProcessControl sub(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> commandFunction,
BiFunction<ShellProcessControl, String, String> terminalCommand);
public abstract CommandProcessControl command(
ShellProcessControl parent,
@NonNull Function<ShellProcessControl, String> command,
Function<ShellProcessControl, String> terminalCommand);
public abstract ShellProcessControl createLocalProcessControl();
}

View file

@ -1,4 +1,4 @@
import io.xpipe.core.impl.LocalProcessControlProvider;
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.source.WriteMode;
import io.xpipe.core.util.CoreJacksonModule;
@ -24,7 +24,7 @@ open module io.xpipe.core {
uses com.fasterxml.jackson.databind.Module;
uses io.xpipe.core.source.WriteMode;
uses LocalProcessControlProvider;
uses ProcessControlProvider;
uses io.xpipe.core.util.ProxyProvider;
uses io.xpipe.core.util.ProxyManagerProvider;
uses io.xpipe.core.util.DataStateProvider;

View file

@ -1,6 +1,6 @@
package io.xpipe.ext.base.actions;
import io.xpipe.app.editor.EditorState;
import io.xpipe.app.util.ExternalEditor;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.store.DataFlow;
@ -33,9 +33,9 @@ public class FileEditAction implements DataStoreActionProvider<FileStore> {
@Override
public void execute(FileStore store) throws Exception {
if (store.getFileSystem().equals(new LocalStore())) {
EditorState.get().openInEditor(store.getFile());
ExternalEditor.get().openInEditor(store.getFile());
} else {
EditorState.get()
ExternalEditor.get()
.startEditing(store.getFileName(), store.getFileExtension(), store, () -> store.openInput(), () -> store.openOutput());
}
}

View file

@ -57,7 +57,7 @@ public class CommandLineTarget implements DataSourceTarget {
@Override
public String getSetupGuideURL() {
return "https://docs.xpipe.io/en/latest/guide/cli/index.html";
return "https://xpipe.io/docs/en/latest/guide/cli/index.html";
}
@Override

View file

@ -118,6 +118,6 @@ public class JavaTarget implements DataSourceTarget {
@Override
public String getSetupGuideURL() {
return "https://docs.xpipe.io/en/latest/api/java.html";
return "https://xpipe.io/docs/en/latest/api/java.html";
}
}

View file

@ -5,7 +5,7 @@ import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.charsetter.StreamCharset;
import io.xpipe.core.impl.FileStore;
import io.xpipe.core.impl.TextSource;
import io.xpipe.extension.util.DaemonExtensionTest;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

View file

@ -5,7 +5,7 @@ import io.xpipe.core.data.node.ValueNode;
import io.xpipe.ext.csv.CsvDelimiter;
import io.xpipe.ext.csv.CsvHeaderState;
import io.xpipe.ext.csv.CsvSource;
import io.xpipe.extension.util.DaemonExtensionTest;
import io.xpipe.extension.test.DaemonExtensionTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

View file

@ -1,51 +0,0 @@
package io.xpipe.extension;
import io.xpipe.core.source.DataSource;
import io.xpipe.extension.event.ErrorEvent;
import javafx.beans.value.ObservableValue;
import javafx.scene.layout.Region;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
public interface DataSourceActionProvider<T extends DataSource<?>> {
static List<DataSourceActionProvider<?>> ALL = new ArrayList<>();
public static void init(ModuleLayer layer) {
if (ALL.size() == 0) {
ALL.addAll(ServiceLoader.load(layer, DataSourceActionProvider.class).stream()
.map(p -> (DataSourceActionProvider<?>) p.get())
.filter(provider -> {
try {
return provider.isActive();
} catch (Throwable e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList());
}
}
Class<T> getApplicableClass();
default boolean isActive() throws Exception {
return true;
}
default boolean isApplicable(T o) throws Exception {
return true;
}
default void applyToRegion(T store, Region region) {
}
ObservableValue<String> getName(T store);
String getIcon(T store);
default void execute(T store) throws Exception {
}
}

View file

@ -31,6 +31,11 @@ public interface DataSourceTarget {
public boolean requiresFullDaemon() {
return true;
}
@Override
public boolean prioritizeLoading() {
return false;
}
}
public static Optional<DataSourceTarget> byId(String id) {

View file

@ -1,19 +1,19 @@
package io.xpipe.extension;
import com.fasterxml.jackson.databind.jsontype.NamedType;
import io.xpipe.core.impl.LocalProcessControlProvider;
import io.xpipe.core.impl.ProcessControlProvider;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ProxyFunction;
import io.xpipe.extension.event.TrackEvent;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ActionProvider;
import io.xpipe.extension.util.ModuleLayerLoader;
import io.xpipe.extension.util.XPipeDaemon;
public class XPipeServiceProviders {
public static void load(ModuleLayer layer) {
LocalProcessControlProvider.init(layer);
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
ModuleLayerLoader.loadAll(layer, hasDaemon, true);
ProcessControlProvider.init(layer);
TrackEvent.info("Loading extension providers ...");
DataSourceProviders.init(layer);
@ -34,14 +34,10 @@ public class XPipeServiceProviders {
});
}
var hasDaemon = XPipeDaemon.getInstanceIfPresent().isPresent();
ModuleLayerLoader.loadAll(layer, hasDaemon);
ModuleLayerLoader.loadAll(layer, hasDaemon, false);
if (hasDaemon) {
ActionProvider.init(layer);
DataSourceActionProvider.init(layer);
ProxyFunction.init(layer);
PrefsProvider.init(layer);
}
TrackEvent.info("Finished loading extension providers");

View file

@ -1,6 +1,7 @@
package io.xpipe.extension.prefs;
import com.dlsc.formsfx.model.structure.Field;
import io.xpipe.extension.util.ModuleLayerLoader;
import javafx.beans.value.ObservableBooleanValue;
import java.util.ServiceLoader;
@ -11,12 +12,24 @@ public abstract class PrefsProvider {
private static Set<PrefsProvider> ALL;
public static void init(ModuleLayer layer) {
if (ALL == null) {
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
ALL = ServiceLoader.load(layer, PrefsProvider.class).stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toSet());
}
@Override
public boolean requiresFullDaemon() {
return true;
}
@Override
public boolean prioritizeLoading() {
return false;
}
}
public static Set<PrefsProvider> getAll() {

View file

@ -1,4 +1,4 @@
package io.xpipe.extension.util;
package io.xpipe.extension.test;
import io.xpipe.api.DataSource;
import io.xpipe.beacon.BeaconDaemonController;

View file

@ -1,4 +1,4 @@
package io.xpipe.extension.util;
package io.xpipe.extension.test;
import io.xpipe.core.data.node.DataStructureNode;
import io.xpipe.core.impl.FileStore;

View file

@ -1,4 +1,4 @@
package io.xpipe.extension.util;
package io.xpipe.extension.test;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.XPipeSession;

View file

@ -1,5 +1,6 @@
package io.xpipe.extension.util;
import io.xpipe.core.source.DataSource;
import io.xpipe.core.store.DataStore;
import io.xpipe.extension.event.ErrorEvent;
import javafx.beans.value.ObservableValue;
@ -7,24 +8,37 @@ import javafx.beans.value.ObservableValue;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
public interface ActionProvider {
static List<ActionProvider> ALL = new ArrayList<>();
public static void init(ModuleLayer layer) {
if (ALL.size() == 0) {
public static class Loader implements ModuleLayerLoader {
@Override
public void init(ModuleLayer layer) {
ALL.addAll(ServiceLoader.load(layer, ActionProvider.class).stream()
.map(p -> (ActionProvider) p.get())
.filter(provider -> {
try {
return provider.isActive();
} catch (Throwable e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.toList());
.map(actionProviderProvider -> actionProviderProvider.get())
.filter(provider -> {
try {
return provider.isActive();
} catch (Throwable e) {
ErrorEvent.fromThrowable(e).handle();
return false;
}
})
.collect(Collectors.toSet()));
}
@Override
public boolean requiresFullDaemon() {
return true;
}
@Override
public boolean prioritizeLoading() {
return false;
}
}
@ -39,10 +53,9 @@ public interface ActionProvider {
return true;
}
interface LauncherCallSite {
String getId();
String getId();
Action createAction(List<String> args) throws Exception;
}
@ -55,6 +68,11 @@ public interface ActionProvider {
return null;
}
default DataSourceCallSite<?> getDataSourceCallSite() {
return null;
}
public static interface DataStoreCallSite<T extends DataStore> {
Action createAction(T store);
@ -64,6 +82,7 @@ public interface ActionProvider {
default boolean isMajor() {
return false;
}
default boolean isApplicable(T o) throws Exception {
return true;
}
@ -76,4 +95,27 @@ public interface ActionProvider {
return true;
}
}
public static interface DataSourceCallSite<T extends DataSource<?>> {
Action createAction(T source);
Class<T> getApplicableClass();
default boolean isMajor() {
return false;
}
default boolean isApplicable(T o) throws Exception {
return true;
}
ObservableValue<String> getName(T source);
String getIcon(T source);
default boolean showIfDisabled() {
return true;
}
}
}

View file

@ -6,13 +6,18 @@ import java.util.ServiceLoader;
public interface ModuleLayerLoader {
public static void loadAll(ModuleLayer layer, boolean hasDaemon) {
public static void loadAll(ModuleLayer layer, boolean hasDaemon, boolean prioritization) {
ServiceLoader.load(layer, ModuleLayerLoader.class).stream().forEach(moduleLayerLoaderProvider -> {
var instance = moduleLayerLoaderProvider.get();
try {
if (instance.requiresFullDaemon() && !hasDaemon) {
return;
}
if (instance.prioritizeLoading() != prioritization) {
return;
}
instance.init(layer);
} catch (Throwable t) {
ErrorEvent.fromThrowable(t).handle();
@ -23,4 +28,6 @@ public interface ModuleLayerLoader {
public void init(ModuleLayer layer);
boolean requiresFullDaemon();
boolean prioritizeLoading();
}

View file

@ -2,6 +2,7 @@ import io.xpipe.core.util.ProxyFunction;
import io.xpipe.extension.DataSourceProvider;
import io.xpipe.extension.DataStoreActionProvider;
import io.xpipe.extension.DataSourceTarget;
import io.xpipe.extension.prefs.PrefsProvider;
import io.xpipe.extension.util.ActionProvider;
import io.xpipe.extension.util.ModuleLayerLoader;
import io.xpipe.extension.util.XPipeDaemon;
@ -51,10 +52,9 @@ open module io.xpipe.extension {
uses io.xpipe.extension.DataStoreProvider;
uses XPipeDaemon;
uses io.xpipe.extension.Cache;
uses io.xpipe.extension.DataSourceActionProvider;
uses ProxyFunction;
uses ActionProvider;
uses io.xpipe.extension.util.ModuleLayerLoader;
provides ModuleLayerLoader with DataSourceTarget.Loader;
provides ModuleLayerLoader with DataSourceTarget.Loader, ActionProvider.Loader, PrefsProvider.Loader;
}