mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-04 03:31:08 +12:00
Implement more robust action system and desktop shortcuts
This commit is contained in:
parent
6f78a357a6
commit
82ff5c7e0f
17 changed files with 331 additions and 87 deletions
|
@ -136,7 +136,7 @@ run {
|
||||||
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
||||||
systemProperty 'io.xpipe.app.developerMode', "true"
|
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||||
systemProperty 'io.xpipe.app.logLevel', "debug"
|
systemProperty 'io.xpipe.app.logLevel', "debug"
|
||||||
systemProperty "io.xpipe.beacon.port", "21724"
|
// systemProperty "io.xpipe.beacon.port", "21724"
|
||||||
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
// systemProperty "io.xpipe.beacon.printMessages", "true"
|
||||||
systemProperty "io.xpipe.app.extensions", extensionDirList
|
systemProperty "io.xpipe.app.extensions", extensionDirList
|
||||||
// systemProperty 'io.xpipe.app.debugPlatform', "true"
|
// systemProperty 'io.xpipe.app.debugPlatform', "true"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package io.xpipe.app.comp.about;
|
package io.xpipe.app.comp.about;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppDistributionType;
|
import io.xpipe.extension.util.XPipeDistributionType;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.grid.AppUpdater;
|
import io.xpipe.app.grid.AppUpdater;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
@ -101,7 +101,7 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAvailable.getValue()) {
|
if (updateAvailable.getValue()) {
|
||||||
return AppDistributionType.get().supportsUpdate()
|
return XPipeDistributionType.get().supportsUpdate()
|
||||||
? I18n.get("downloadUpdate")
|
? I18n.get("downloadUpdate")
|
||||||
: I18n.get("checkOutUpdate");
|
: I18n.get("checkOutUpdate");
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,7 +134,7 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAvailable.getValue() && !AppDistributionType.get().supportsUpdate()) {
|
if (updateAvailable.getValue() && !XPipeDistributionType.get().supportsUpdate()) {
|
||||||
Hyperlinks.open(
|
Hyperlinks.open(
|
||||||
AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl());
|
AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl());
|
||||||
} else if (updateAvailable.getValue()) {
|
} else if (updateAvailable.getValue()) {
|
||||||
|
|
|
@ -163,7 +163,7 @@ public class StoreEntryComp extends SimpleComp {
|
||||||
private Comp<?> createButtonBar() {
|
private Comp<?> createButtonBar() {
|
||||||
var list = new ArrayList<Comp<?>>();
|
var list = new ArrayList<Comp<?>>();
|
||||||
for (var p : entry.getActionProviders().entrySet()) {
|
for (var p : entry.getActionProviders().entrySet()) {
|
||||||
var actionProvider = p.getKey();
|
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||||
if (!actionProvider.isMajor()) {
|
if (!actionProvider.isMajor()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,8 @@ public class StoreEntryComp extends SimpleComp {
|
||||||
var button = new IconButtonComp(
|
var button = new IconButtonComp(
|
||||||
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
|
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
actionProvider.execute(entry.getEntry().getStore().asNeeded());
|
var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded());
|
||||||
|
action.execute();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
button.apply(new FancyTooltipAugment<>(
|
button.apply(new FancyTooltipAugment<>(
|
||||||
|
@ -216,7 +217,7 @@ public class StoreEntryComp extends SimpleComp {
|
||||||
AppFont.normal(contextMenu.getStyleableNode());
|
AppFont.normal(contextMenu.getStyleableNode());
|
||||||
|
|
||||||
for (var p : entry.getActionProviders().entrySet()) {
|
for (var p : entry.getActionProviders().entrySet()) {
|
||||||
var actionProvider = p.getKey();
|
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||||
if (actionProvider.isMajor()) {
|
if (actionProvider.isMajor()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -226,7 +227,8 @@ public class StoreEntryComp extends SimpleComp {
|
||||||
var item = new MenuItem(null, new FontIcon(icon));
|
var item = new MenuItem(null, new FontIcon(icon));
|
||||||
item.setOnAction(event -> {
|
item.setOnAction(event -> {
|
||||||
ThreadHelper.runFailableAsync(() -> {
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
actionProvider.execute(entry.getEntry().getStore().asNeeded());
|
var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded());
|
||||||
|
action.execute();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
item.textProperty().bind(name);
|
item.textProperty().bind(name);
|
||||||
|
|
|
@ -5,9 +5,9 @@ import io.xpipe.app.comp.storage.StorageFilter;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
import io.xpipe.app.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
import io.xpipe.extension.DataStoreActionProvider;
|
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import io.xpipe.extension.fxcomps.util.PlatformThread;
|
import io.xpipe.extension.fxcomps.util.PlatformThread;
|
||||||
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.*;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
import javafx.beans.value.ObservableBooleanValue;
|
||||||
|
@ -29,7 +29,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
||||||
private final Property<DataStoreEntry.State> state = new SimpleObjectProperty<>();
|
private final Property<DataStoreEntry.State> state = new SimpleObjectProperty<>();
|
||||||
private final StringProperty information = new SimpleStringProperty();
|
private final StringProperty information = new SimpleStringProperty();
|
||||||
private final StringProperty summary = new SimpleStringProperty();
|
private final StringProperty summary = new SimpleStringProperty();
|
||||||
private final Map<DataStoreActionProvider<?>, ObservableBooleanValue> actionProviders;
|
private final Map<ActionProvider, ObservableBooleanValue> actionProviders;
|
||||||
private final BooleanProperty editable = new SimpleBooleanProperty();
|
private final BooleanProperty editable = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty renamable = new SimpleBooleanProperty();
|
private final BooleanProperty renamable = new SimpleBooleanProperty();
|
||||||
private final BooleanProperty refreshable = new SimpleBooleanProperty();
|
private final BooleanProperty refreshable = new SimpleBooleanProperty();
|
||||||
|
@ -40,10 +40,10 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
||||||
this.name = new SimpleStringProperty(entry.getName());
|
this.name = new SimpleStringProperty(entry.getName());
|
||||||
this.lastAccess = new SimpleObjectProperty<>(entry.getLastAccess().minus(Duration.ofMillis(500)));
|
this.lastAccess = new SimpleObjectProperty<>(entry.getLastAccess().minus(Duration.ofMillis(500)));
|
||||||
this.actionProviders = new LinkedHashMap<>();
|
this.actionProviders = new LinkedHashMap<>();
|
||||||
DataStoreActionProvider.ALL.stream()
|
ActionProvider.ALL.stream()
|
||||||
.filter(dataStoreActionProvider -> {
|
.filter(dataStoreActionProvider -> {
|
||||||
return !entry.isDisabled()
|
return !entry.isDisabled()
|
||||||
&& dataStoreActionProvider
|
&& dataStoreActionProvider.getDataStoreCallSite() != null && dataStoreActionProvider.getDataStoreCallSite()
|
||||||
.getApplicableClass()
|
.getApplicableClass()
|
||||||
.isAssignableFrom(entry.getStore().getClass());
|
.isAssignableFrom(entry.getStore().getClass());
|
||||||
})
|
})
|
||||||
|
@ -54,7 +54,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataStoreActionProvider.isApplicable(
|
return dataStoreActionProvider.getDataStoreCallSite().isApplicable(
|
||||||
entry.getStore().asNeeded());
|
entry.getStore().asNeeded());
|
||||||
},
|
},
|
||||||
disabledProperty(),
|
disabledProperty(),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.xpipe.app.grid;
|
package io.xpipe.app.grid;
|
||||||
|
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.core.AppDistributionType;
|
import io.xpipe.extension.util.XPipeDistributionType;
|
||||||
import io.xpipe.app.core.AppExtensionManager;
|
import io.xpipe.app.core.AppExtensionManager;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
|
@ -54,7 +54,7 @@ public class AppUpdater {
|
||||||
.equals(AppProperties.get().getVersion())) {
|
.equals(AppProperties.get().getVersion())) {
|
||||||
downloadedUpdate.setValue(null);
|
downloadedUpdate.setValue(null);
|
||||||
}
|
}
|
||||||
if (!AppDistributionType.get().supportsUpdate()) {
|
if (!XPipeDistributionType.get().supportsUpdate()) {
|
||||||
downloadedUpdate.setValue(null);
|
downloadedUpdate.setValue(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +124,8 @@ public class AppUpdater {
|
||||||
|
|
||||||
INSTANCE = new AppUpdater();
|
INSTANCE = new AppUpdater();
|
||||||
|
|
||||||
if (AppDistributionType.get().supportsUpdate()
|
if (XPipeDistributionType.get().supportsUpdate()
|
||||||
&& AppDistributionType.get() != AppDistributionType.DEVELOPMENT) {
|
&& XPipeDistributionType.get() != XPipeDistributionType.DEVELOPMENT) {
|
||||||
ThreadHelper.create("updater", true, () -> {
|
ThreadHelper.create("updater", true, () -> {
|
||||||
ThreadHelper.sleep(Duration.ofMinutes(10).toMillis());
|
ThreadHelper.sleep(Duration.ofMinutes(10).toMillis());
|
||||||
event("Starting background updater thread");
|
event("Starting background updater thread");
|
||||||
|
@ -182,7 +182,7 @@ public class AppUpdater {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AppDistributionType.get().supportsUpdate()) {
|
if (!XPipeDistributionType.get().supportsUpdate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import io.sentry.*;
|
||||||
import io.sentry.protocol.SentryId;
|
import io.sentry.protocol.SentryId;
|
||||||
import io.sentry.protocol.User;
|
import io.sentry.protocol.User;
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.core.AppDistributionType;
|
import io.xpipe.extension.util.XPipeDistributionType;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
@ -22,7 +22,7 @@ public class SentryErrorHandler {
|
||||||
options.setEnableUncaughtExceptionHandler(false);
|
options.setEnableUncaughtExceptionHandler(false);
|
||||||
options.setAttachServerName(false);
|
options.setAttachServerName(false);
|
||||||
// options.setDebug(true);
|
// options.setDebug(true);
|
||||||
options.setDist(AppDistributionType.get().getName());
|
options.setDist(XPipeDistributionType.get().getName());
|
||||||
options.setRelease(AppProperties.get().getVersion());
|
options.setRelease(AppProperties.get().getVersion());
|
||||||
options.setEnableShutdownHook(false);
|
options.setEnableShutdownHook(false);
|
||||||
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());
|
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
package io.xpipe.app.launcher;
|
package io.xpipe.app.launcher;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
|
import io.xpipe.app.comp.source.GuiDsCreatorMultiStep;
|
||||||
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
|
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.storage.DataStoreEntry;
|
|
||||||
import io.xpipe.core.impl.FileStore;
|
import io.xpipe.core.impl.FileStore;
|
||||||
import io.xpipe.core.store.DataStore;
|
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
|
||||||
import io.xpipe.core.util.SecretValue;
|
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
import lombok.EqualsAndHashCode;
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
@ -22,12 +16,11 @@ import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public abstract class LauncherInput {
|
public abstract class LauncherInput {
|
||||||
|
|
||||||
public static void handle(List<String> arguments) {
|
public static void handle(List<String> arguments) {
|
||||||
var all = new ArrayList<LauncherInput>();
|
var all = new ArrayList<ActionProvider.Action>();
|
||||||
arguments.forEach(s -> {
|
arguments.forEach(s -> {
|
||||||
try {
|
try {
|
||||||
all.addAll(of(s));
|
all.addAll(of(s));
|
||||||
|
@ -55,11 +48,7 @@ public abstract class LauncherInput {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void execute() throws Exception;
|
public static List<ActionProvider.Action> of(String input) {
|
||||||
|
|
||||||
public abstract boolean requiresPlatform();
|
|
||||||
|
|
||||||
public static List<LauncherInput> of(String input) {
|
|
||||||
if (input.startsWith("\"") && input.endsWith("\"")) {
|
if (input.startsWith("\"") && input.endsWith("\"")) {
|
||||||
input = input.substring(1, input.length() - 1);
|
input = input.substring(1, input.length() - 1);
|
||||||
}
|
}
|
||||||
|
@ -76,13 +65,22 @@ public abstract class LauncherInput {
|
||||||
if (scheme.equalsIgnoreCase("xpipe")) {
|
if (scheme.equalsIgnoreCase("xpipe")) {
|
||||||
var action = uri.getAuthority();
|
var action = uri.getAuthority();
|
||||||
var args = Arrays.asList(uri.getPath().split("/"));
|
var args = Arrays.asList(uri.getPath().split("/"));
|
||||||
|
var found = ActionProvider.ALL.stream()
|
||||||
var a = switch (action.toLowerCase()) {
|
.filter(actionProvider -> actionProvider.getLauncherCallSite() != null
|
||||||
case "add" -> new AddActionInput(args);
|
&& actionProvider
|
||||||
default -> throw new IllegalStateException("Unexpected value: " + action);
|
.getLauncherCallSite()
|
||||||
};
|
.getId()
|
||||||
|
.equalsIgnoreCase(action))
|
||||||
return List.of(a);
|
.findFirst();
|
||||||
|
if (found.isPresent()) {
|
||||||
|
ActionProvider.Action a = null;
|
||||||
|
try {
|
||||||
|
a = found.get().getLauncherCallSite().createAction(args);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return List.of(a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
@ -96,13 +94,11 @@ public abstract class LauncherInput {
|
||||||
} catch (InvalidPathException ignored) {
|
} catch (InvalidPathException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return List.of();
|
return List.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@EqualsAndHashCode(callSuper = true)
|
public static class LocalFileInput implements ActionProvider.Action {
|
||||||
public static class LocalFileInput extends LauncherInput {
|
|
||||||
|
|
||||||
Path file;
|
Path file;
|
||||||
|
|
||||||
|
@ -125,7 +121,7 @@ public abstract class LauncherInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class ActionInput extends LauncherInput {
|
public abstract static class ActionInput extends LauncherInput {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final List<String> args;
|
private final List<String> args;
|
||||||
|
@ -134,31 +130,4 @@ public abstract class LauncherInput {
|
||||||
this.args = args;
|
this.args = args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
public static class AddActionInput extends ActionInput {
|
|
||||||
|
|
||||||
public AddActionInput(List<String> args) {
|
|
||||||
super(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute() throws JsonProcessingException {
|
|
||||||
var type = getArgs().get(1);
|
|
||||||
var storeString = SecretValue.ofSecret(getArgs().get(2));
|
|
||||||
var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class);
|
|
||||||
if (store == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entry = DataStoreEntry.createNew(UUID.randomUUID(),"", store);
|
|
||||||
GuiDsStoreCreator.showEdit(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean requiresPlatform() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import com.dlsc.preferencesfx.model.Category;
|
||||||
import com.dlsc.preferencesfx.model.Group;
|
import com.dlsc.preferencesfx.model.Group;
|
||||||
import com.dlsc.preferencesfx.model.Setting;
|
import com.dlsc.preferencesfx.model.Setting;
|
||||||
import com.dlsc.preferencesfx.util.VisibilityProperty;
|
import com.dlsc.preferencesfx.util.VisibilityProperty;
|
||||||
import io.xpipe.app.core.AppDistributionType;
|
import io.xpipe.extension.util.XPipeDistributionType;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.core.AppStyle;
|
import io.xpipe.app.core.AppStyle;
|
||||||
import io.xpipe.extension.event.ErrorEvent;
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
@ -116,9 +116,9 @@ public class AppPrefs {
|
||||||
// Automatically update
|
// Automatically update
|
||||||
// ====================
|
// ====================
|
||||||
private final BooleanProperty automaticallyUpdate =
|
private final BooleanProperty automaticallyUpdate =
|
||||||
typed(new SimpleBooleanProperty(AppDistributionType.get().supportsUpdate()), Boolean.class);
|
typed(new SimpleBooleanProperty(XPipeDistributionType.get().supportsUpdate()), Boolean.class);
|
||||||
private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate)
|
private final BooleanField automaticallyUpdateField = BooleanField.ofBooleanType(automaticallyUpdate)
|
||||||
.editable(AppDistributionType.get().supportsUpdate())
|
.editable(XPipeDistributionType.get().supportsUpdate())
|
||||||
.render(() -> new ToggleControl());
|
.render(() -> new ToggleControl());
|
||||||
private final BooleanProperty updateToPrereleases = typed(new SimpleBooleanProperty(true), Boolean.class);
|
private final BooleanProperty updateToPrereleases = typed(new SimpleBooleanProperty(true), Boolean.class);
|
||||||
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
|
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
|
||||||
|
|
|
@ -177,6 +177,31 @@ public class XPipeInstallation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Path getLocalDefaultInstallationIcon() {
|
||||||
|
Path path = getLocalInstallationBasePath();
|
||||||
|
|
||||||
|
// Check for development environment
|
||||||
|
if (!ModuleHelper.isImage()) {
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
return path.resolve("dist").resolve("logo").resolve("logo.ico");
|
||||||
|
} else if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
return path.resolve("dist").resolve("logo").resolve("logo.png");
|
||||||
|
} else {
|
||||||
|
return path.resolve("dist").resolve("logo").resolve("logo.icns");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
return path.resolve("app").resolve("logo.ico");
|
||||||
|
} else if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
return path.resolve("logo.png");
|
||||||
|
} else {
|
||||||
|
return path.resolve("Contents")
|
||||||
|
.resolve("Resources")
|
||||||
|
.resolve("logo.icns");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String getLocalDefaultInstallationBasePath(boolean acceptCustomHome) {
|
public static String getLocalDefaultInstallationBasePath(boolean acceptCustomHome) {
|
||||||
var customHome = System.getenv("XPIPE_HOME");
|
var customHome = System.getenv("XPIPE_HOME");
|
||||||
if (customHome != null && !customHome.isEmpty() && acceptCustomHome) {
|
if (customHome != null && !customHome.isEmpty() && acceptCustomHome) {
|
||||||
|
|
13
dist/base.gradle
vendored
13
dist/base.gradle
vendored
|
@ -22,6 +22,10 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||||
from "$distDir/jpackage/xpiped"
|
from "$distDir/jpackage/xpiped"
|
||||||
into "$distDir/base/app"
|
into "$distDir/base/app"
|
||||||
}
|
}
|
||||||
|
copy {
|
||||||
|
from "$projectDir/logo/logo.ico"
|
||||||
|
into "$distDir/base/app"
|
||||||
|
}
|
||||||
|
|
||||||
file("$distDir/base/app/xpiped.exe").writable = true
|
file("$distDir/base/app/xpiped.exe").writable = true
|
||||||
exec {
|
exec {
|
||||||
|
@ -86,7 +90,12 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||||
from "$distDir/jpackage/xpiped"
|
from "$distDir/jpackage/xpiped"
|
||||||
into "$distDir/base/app"
|
into "$distDir/base/app"
|
||||||
}
|
}
|
||||||
|
copy {
|
||||||
|
from "$projectDir/logo/logo.png"
|
||||||
|
into "$distDir/base/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixes a JPackage bug
|
||||||
copy {
|
copy {
|
||||||
from "$distDir/base/app/lib/app/xpiped.cfg"
|
from "$distDir/base/app/lib/app/xpiped.cfg"
|
||||||
into "$distDir/base/app"
|
into "$distDir/base/app"
|
||||||
|
@ -135,6 +144,10 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||||
from "$distDir/jpackage/xpiped.app/Contents"
|
from "$distDir/jpackage/xpiped.app/Contents"
|
||||||
into "$distDir/X-Pipe.app/Contents/"
|
into "$distDir/X-Pipe.app/Contents/"
|
||||||
}
|
}
|
||||||
|
copy {
|
||||||
|
from "$projectDir/logo/logo.icns"
|
||||||
|
into "$distDir/X-Pipe.app/Contents/Resources/"
|
||||||
|
}
|
||||||
copy {
|
copy {
|
||||||
from "$distDir/cli/xpipe"
|
from "$distDir/cli/xpipe"
|
||||||
into "$distDir/X-Pipe.app/Contents/MacOS/"
|
into "$distDir/X-Pipe.app/Contents/MacOS/"
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package io.xpipe.ext.base.actions;
|
||||||
|
|
||||||
|
import io.xpipe.app.comp.source.store.GuiDsStoreCreator;
|
||||||
|
import io.xpipe.app.storage.DataStoreEntry;
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
import io.xpipe.core.util.SecretValue;
|
||||||
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
|
import lombok.Value;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class AddStoreAction implements ActionProvider {
|
||||||
|
@Value
|
||||||
|
static class Action implements ActionProvider.Action {
|
||||||
|
|
||||||
|
DataStore store;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresPlatform() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void execute() throws Exception {
|
||||||
|
if (store == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = DataStoreEntry.createNew(UUID.randomUUID(), "", store);
|
||||||
|
GuiDsStoreCreator.showEdit(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LauncherCallSite getLauncherCallSite() {
|
||||||
|
return new LauncherCallSite() {
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "addStore";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Action createAction(List<String> args) throws Exception {
|
||||||
|
var storeString = SecretValue.ofSecret(args.get(1));
|
||||||
|
var store = JacksonMapper.parse(storeString.getSecretValue(), DataStore.class);
|
||||||
|
return new Action(store);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,11 @@
|
||||||
import io.xpipe.ext.base.*;
|
import io.xpipe.ext.base.*;
|
||||||
import io.xpipe.ext.base.actions.FileBrowseAction;
|
import io.xpipe.ext.base.actions.*;
|
||||||
import io.xpipe.ext.base.actions.FileEditAction;
|
|
||||||
import io.xpipe.ext.base.actions.ShareStoreAction;
|
|
||||||
import io.xpipe.ext.base.actions.StreamExportAction;
|
|
||||||
import io.xpipe.ext.base.apps.*;
|
import io.xpipe.ext.base.apps.*;
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.DataStoreActionProvider;
|
import io.xpipe.extension.DataStoreActionProvider;
|
||||||
import io.xpipe.extension.DataStoreProvider;
|
import io.xpipe.extension.DataStoreProvider;
|
||||||
import io.xpipe.extension.DataSourceTarget;
|
import io.xpipe.extension.DataSourceTarget;
|
||||||
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
|
|
||||||
open module io.xpipe.ext.base {
|
open module io.xpipe.ext.base {
|
||||||
exports io.xpipe.ext.base;
|
exports io.xpipe.ext.base;
|
||||||
|
@ -24,6 +22,7 @@ open module io.xpipe.ext.base {
|
||||||
requires static net.synedra.validatorfx;
|
requires static net.synedra.validatorfx;
|
||||||
requires static io.xpipe.app;
|
requires static io.xpipe.app;
|
||||||
|
|
||||||
|
provides ActionProvider with AddStoreAction;
|
||||||
provides DataStoreActionProvider with
|
provides DataStoreActionProvider with
|
||||||
StreamExportAction,
|
StreamExportAction,
|
||||||
ShareStoreAction,
|
ShareStoreAction,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.core.util.ProxyFunction;
|
import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
import io.xpipe.extension.prefs.PrefsProvider;
|
import io.xpipe.extension.prefs.PrefsProvider;
|
||||||
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
import io.xpipe.extension.util.ModuleLayerLoader;
|
import io.xpipe.extension.util.ModuleLayerLoader;
|
||||||
import io.xpipe.extension.util.XPipeDaemon;
|
import io.xpipe.extension.util.XPipeDaemon;
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ public class XPipeServiceProviders {
|
||||||
ModuleLayerLoader.loadAll(layer, hasDaemon);
|
ModuleLayerLoader.loadAll(layer, hasDaemon);
|
||||||
|
|
||||||
if (hasDaemon) {
|
if (hasDaemon) {
|
||||||
DataStoreActionProvider.init(layer);
|
ActionProvider.init(layer);
|
||||||
DataSourceActionProvider.init(layer);
|
DataSourceActionProvider.init(layer);
|
||||||
ProxyFunction.init(layer);
|
ProxyFunction.init(layer);
|
||||||
PrefsProvider.init(layer);
|
PrefsProvider.init(layer);
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
public interface ActionProvider {
|
||||||
|
|
||||||
|
static List<ActionProvider> ALL = new ArrayList<>();
|
||||||
|
|
||||||
|
public static void init(ModuleLayer layer) {
|
||||||
|
if (ALL.size() == 0) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Action {
|
||||||
|
|
||||||
|
boolean requiresPlatform();
|
||||||
|
|
||||||
|
void execute() throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isActive() throws Exception {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface LauncherCallSite {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
Action createAction(List<String> args) throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
default LauncherCallSite getLauncherCallSite() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default DataStoreCallSite<?> getDataStoreCallSite() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static interface DataStoreCallSite<T extends DataStore> {
|
||||||
|
|
||||||
|
Action createAction(T store);
|
||||||
|
|
||||||
|
Class<T> getApplicableClass();
|
||||||
|
|
||||||
|
default boolean isMajor() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default boolean isApplicable(T o) throws Exception {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableValue<String> getName(T store);
|
||||||
|
|
||||||
|
String getIcon(T store);
|
||||||
|
|
||||||
|
default boolean showIfDisabled() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.store.ShellStore;
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class DesktopShortcuts {
|
||||||
|
|
||||||
|
private static void createWindowsShortcut(String target, String name) throws Exception {
|
||||||
|
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
|
||||||
|
var content = String.format(
|
||||||
|
"""
|
||||||
|
set "TARGET=%s"
|
||||||
|
set "SHORTCUT=%%HOMEDRIVE%%%%HOMEPATH%%\\Desktop\\%s.lnk"
|
||||||
|
set PWS=powershell.exe -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile
|
||||||
|
|
||||||
|
%%PWS%% -Command "$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%%SHORTCUT%%'); $S.IconLocation='%s'; $S.TargetPath = '%%TARGET%%'; $S.Save()"
|
||||||
|
""",
|
||||||
|
target, name, icon.toString());
|
||||||
|
ShellStore.local().create().executeSimpleCommand(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createLinuxShortcut(String target, String name) throws Exception {
|
||||||
|
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
|
||||||
|
var content = String.format(
|
||||||
|
"""
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=%s
|
||||||
|
Comment=Open with X-Pipe
|
||||||
|
TryExec=/opt/xpipe/app/bin/xpiped
|
||||||
|
Exec=/opt/xpipe/cli/bin/xpipe open %s
|
||||||
|
Icon=%s
|
||||||
|
Terminal=false
|
||||||
|
Categories=Utility;Development;Office;
|
||||||
|
""",
|
||||||
|
name, target, icon.toString());
|
||||||
|
var file = Path.of("~/Desktop/" + name + ".desktop").toRealPath();
|
||||||
|
Files.writeString(file, content);
|
||||||
|
file.toFile().setExecutable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createMacOSShortcut(String target, String name) throws Exception {
|
||||||
|
var icon = XPipeInstallation.getLocalDefaultInstallationIcon();
|
||||||
|
var content = String.format(
|
||||||
|
"""
|
||||||
|
#!/bin/bash
|
||||||
|
open %s
|
||||||
|
""",
|
||||||
|
target);
|
||||||
|
var file = Path.of("~/Desktop/" + name + ".command").toRealPath();
|
||||||
|
Files.writeString(file, content);
|
||||||
|
file.toFile().setExecutable(true);
|
||||||
|
|
||||||
|
var iconScriptContent = String.format(
|
||||||
|
"""
|
||||||
|
iconSource="%s"
|
||||||
|
iconDestination="%s"
|
||||||
|
icon=/tmp/`basename $iconSource`
|
||||||
|
rsrc=/tmp/icon.rsrc
|
||||||
|
cp $iconSource $icon
|
||||||
|
sips -i $icon
|
||||||
|
DeRez -only icns $icon > $rsrc
|
||||||
|
SetFile -a C $iconDestination
|
||||||
|
Rez -append $rsrc -o $iconDestination
|
||||||
|
""",
|
||||||
|
icon, target);
|
||||||
|
ShellStore.local().create().executeSimpleCommand(iconScriptContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void create(String target, String name) throws Exception {
|
||||||
|
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||||
|
createWindowsShortcut(target, name);
|
||||||
|
} else if (OsType.getLocal().equals(OsType.LINUX)) {
|
||||||
|
createLinuxShortcut(target, name);
|
||||||
|
} else {
|
||||||
|
createMacOSShortcut(target, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
package io.xpipe.app.core;
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.core.process.OsType;
|
||||||
|
import io.xpipe.core.util.ModuleHelper;
|
||||||
import io.xpipe.core.util.XPipeInstallation;
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
import io.xpipe.extension.event.TrackEvent;
|
import io.xpipe.extension.event.TrackEvent;
|
||||||
|
|
||||||
public interface AppDistributionType {
|
public interface XPipeDistributionType {
|
||||||
|
|
||||||
AppDistributionType DEVELOPMENT = new AppDistributionType() {
|
XPipeDistributionType DEVELOPMENT = new XPipeDistributionType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsUpdate() {
|
public boolean supportsUpdate() {
|
||||||
|
@ -17,12 +19,18 @@ public interface AppDistributionType {
|
||||||
TrackEvent.info("Development mode update executed");
|
TrackEvent.info("Development mode update executed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsURLs() {
|
||||||
|
// Enabled for testing
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "development";
|
return "development";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
AppDistributionType PORTABLE = new AppDistributionType() {
|
XPipeDistributionType PORTABLE = new XPipeDistributionType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsUpdate() {
|
public boolean supportsUpdate() {
|
||||||
|
@ -32,12 +40,17 @@ public interface AppDistributionType {
|
||||||
@Override
|
@Override
|
||||||
public void performUpdateAction() {}
|
public void performUpdateAction() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsURLs() {
|
||||||
|
return OsType.getLocal().equals(OsType.MAC);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "portable";
|
return "portable";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
AppDistributionType INSTALLATION = new AppDistributionType() {
|
XPipeDistributionType INSTALLATION = new XPipeDistributionType() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsUpdate() {
|
public boolean supportsUpdate() {
|
||||||
|
@ -49,14 +62,19 @@ public interface AppDistributionType {
|
||||||
TrackEvent.info("Update action called");
|
TrackEvent.info("Update action called");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsURLs() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "install";
|
return "install";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static AppDistributionType get() {
|
static XPipeDistributionType get() {
|
||||||
if (!AppProperties.get().isImage()) {
|
if (!ModuleHelper.isImage()) {
|
||||||
return DEVELOPMENT;
|
return DEVELOPMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,5 +89,7 @@ public interface AppDistributionType {
|
||||||
|
|
||||||
void performUpdateAction();
|
void performUpdateAction();
|
||||||
|
|
||||||
|
boolean supportsURLs();
|
||||||
|
|
||||||
String getName();
|
String getName();
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ import io.xpipe.core.util.ProxyFunction;
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
import io.xpipe.extension.DataStoreActionProvider;
|
import io.xpipe.extension.DataStoreActionProvider;
|
||||||
import io.xpipe.extension.DataSourceTarget;
|
import io.xpipe.extension.DataSourceTarget;
|
||||||
|
import io.xpipe.extension.util.ActionProvider;
|
||||||
import io.xpipe.extension.util.ModuleLayerLoader;
|
import io.xpipe.extension.util.ModuleLayerLoader;
|
||||||
import io.xpipe.extension.util.XPipeDaemon;
|
import io.xpipe.extension.util.XPipeDaemon;
|
||||||
|
|
||||||
|
@ -52,6 +53,7 @@ open module io.xpipe.extension {
|
||||||
uses io.xpipe.extension.Cache;
|
uses io.xpipe.extension.Cache;
|
||||||
uses io.xpipe.extension.DataSourceActionProvider;
|
uses io.xpipe.extension.DataSourceActionProvider;
|
||||||
uses ProxyFunction;
|
uses ProxyFunction;
|
||||||
|
uses ActionProvider;
|
||||||
uses io.xpipe.extension.util.ModuleLayerLoader;
|
uses io.xpipe.extension.util.ModuleLayerLoader;
|
||||||
|
|
||||||
provides ModuleLayerLoader with DataSourceTarget.Loader;
|
provides ModuleLayerLoader with DataSourceTarget.Loader;
|
||||||
|
|
Loading…
Reference in a new issue