mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-09-30 00:56:56 +13:00
Implement more robust action system and desktop shortcuts
This commit is contained in:
parent
3eb165cce7
commit
6d07f5bab5
17 changed files with 331 additions and 87 deletions
|
@ -136,7 +136,7 @@ run {
|
|||
systemProperty 'io.xpipe.app.writeSysOut', "true"
|
||||
systemProperty 'io.xpipe.app.developerMode', "true"
|
||||
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.app.extensions", extensionDirList
|
||||
// systemProperty 'io.xpipe.app.debugPlatform', "true"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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.grid.AppUpdater;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
|
@ -101,7 +101,7 @@ public class UpdateCheckComp extends SimpleComp {
|
|||
}
|
||||
|
||||
if (updateAvailable.getValue()) {
|
||||
return AppDistributionType.get().supportsUpdate()
|
||||
return XPipeDistributionType.get().supportsUpdate()
|
||||
? I18n.get("downloadUpdate")
|
||||
: I18n.get("checkOutUpdate");
|
||||
} else {
|
||||
|
@ -134,7 +134,7 @@ public class UpdateCheckComp extends SimpleComp {
|
|||
return;
|
||||
}
|
||||
|
||||
if (updateAvailable.getValue() && !AppDistributionType.get().supportsUpdate()) {
|
||||
if (updateAvailable.getValue() && !XPipeDistributionType.get().supportsUpdate()) {
|
||||
Hyperlinks.open(
|
||||
AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl());
|
||||
} else if (updateAvailable.getValue()) {
|
||||
|
|
|
@ -163,7 +163,7 @@ public class StoreEntryComp extends SimpleComp {
|
|||
private Comp<?> createButtonBar() {
|
||||
var list = new ArrayList<Comp<?>>();
|
||||
for (var p : entry.getActionProviders().entrySet()) {
|
||||
var actionProvider = p.getKey();
|
||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||
if (!actionProvider.isMajor()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -171,7 +171,8 @@ public class StoreEntryComp extends SimpleComp {
|
|||
var button = new IconButtonComp(
|
||||
actionProvider.getIcon(entry.getEntry().getStore().asNeeded()), () -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
actionProvider.execute(entry.getEntry().getStore().asNeeded());
|
||||
var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
button.apply(new FancyTooltipAugment<>(
|
||||
|
@ -216,7 +217,7 @@ public class StoreEntryComp extends SimpleComp {
|
|||
AppFont.normal(contextMenu.getStyleableNode());
|
||||
|
||||
for (var p : entry.getActionProviders().entrySet()) {
|
||||
var actionProvider = p.getKey();
|
||||
var actionProvider = p.getKey().getDataStoreCallSite();
|
||||
if (actionProvider.isMajor()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -226,7 +227,8 @@ public class StoreEntryComp extends SimpleComp {
|
|||
var item = new MenuItem(null, new FontIcon(icon));
|
||||
item.setOnAction(event -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
actionProvider.execute(entry.getEntry().getStore().asNeeded());
|
||||
var action = actionProvider.createAction(entry.getEntry().getStore().asNeeded());
|
||||
action.execute();
|
||||
});
|
||||
});
|
||||
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.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.extension.DataStoreActionProvider;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
import io.xpipe.extension.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.extension.util.ActionProvider;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
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 StringProperty information = 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 renamable = 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.lastAccess = new SimpleObjectProperty<>(entry.getLastAccess().minus(Duration.ofMillis(500)));
|
||||
this.actionProviders = new LinkedHashMap<>();
|
||||
DataStoreActionProvider.ALL.stream()
|
||||
ActionProvider.ALL.stream()
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return !entry.isDisabled()
|
||||
&& dataStoreActionProvider
|
||||
&& dataStoreActionProvider.getDataStoreCallSite() != null && dataStoreActionProvider.getDataStoreCallSite()
|
||||
.getApplicableClass()
|
||||
.isAssignableFrom(entry.getStore().getClass());
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ public class StoreEntryWrapper implements StorageFilter.Filterable {
|
|||
return false;
|
||||
}
|
||||
|
||||
return dataStoreActionProvider.isApplicable(
|
||||
return dataStoreActionProvider.getDataStoreCallSite().isApplicable(
|
||||
entry.getStore().asNeeded());
|
||||
},
|
||||
disabledProperty(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.grid;
|
||||
|
||||
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.AppProperties;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
|
@ -54,7 +54,7 @@ public class AppUpdater {
|
|||
.equals(AppProperties.get().getVersion())) {
|
||||
downloadedUpdate.setValue(null);
|
||||
}
|
||||
if (!AppDistributionType.get().supportsUpdate()) {
|
||||
if (!XPipeDistributionType.get().supportsUpdate()) {
|
||||
downloadedUpdate.setValue(null);
|
||||
}
|
||||
|
||||
|
@ -124,8 +124,8 @@ public class AppUpdater {
|
|||
|
||||
INSTANCE = new AppUpdater();
|
||||
|
||||
if (AppDistributionType.get().supportsUpdate()
|
||||
&& AppDistributionType.get() != AppDistributionType.DEVELOPMENT) {
|
||||
if (XPipeDistributionType.get().supportsUpdate()
|
||||
&& XPipeDistributionType.get() != XPipeDistributionType.DEVELOPMENT) {
|
||||
ThreadHelper.create("updater", true, () -> {
|
||||
ThreadHelper.sleep(Duration.ofMinutes(10).toMillis());
|
||||
event("Starting background updater thread");
|
||||
|
@ -182,7 +182,7 @@ public class AppUpdater {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!AppDistributionType.get().supportsUpdate()) {
|
||||
if (!XPipeDistributionType.get().supportsUpdate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import io.sentry.*;
|
|||
import io.sentry.protocol.SentryId;
|
||||
import io.sentry.protocol.User;
|
||||
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.extension.event.ErrorEvent;
|
||||
import io.xpipe.extension.event.TrackEvent;
|
||||
|
@ -22,7 +22,7 @@ public class SentryErrorHandler {
|
|||
options.setEnableUncaughtExceptionHandler(false);
|
||||
options.setAttachServerName(false);
|
||||
// options.setDebug(true);
|
||||
options.setDist(AppDistributionType.get().getName());
|
||||
options.setDist(XPipeDistributionType.get().getName());
|
||||
options.setRelease(AppProperties.get().getVersion());
|
||||
options.setEnableShutdownHook(false);
|
||||
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
package io.xpipe.app.launcher;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
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.storage.DataStoreEntry;
|
||||
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.event.ErrorEvent;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import io.xpipe.extension.util.ActionProvider;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
|
@ -22,12 +16,11 @@ import java.nio.file.Path;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LauncherInput {
|
||||
|
||||
public static void handle(List<String> arguments) {
|
||||
var all = new ArrayList<LauncherInput>();
|
||||
var all = new ArrayList<ActionProvider.Action>();
|
||||
arguments.forEach(s -> {
|
||||
try {
|
||||
all.addAll(of(s));
|
||||
|
@ -55,11 +48,7 @@ public abstract class LauncherInput {
|
|||
});
|
||||
}
|
||||
|
||||
public abstract void execute() throws Exception;
|
||||
|
||||
public abstract boolean requiresPlatform();
|
||||
|
||||
public static List<LauncherInput> of(String input) {
|
||||
public static List<ActionProvider.Action> of(String input) {
|
||||
if (input.startsWith("\"") && input.endsWith("\"")) {
|
||||
input = input.substring(1, input.length() - 1);
|
||||
}
|
||||
|
@ -76,13 +65,22 @@ public abstract class LauncherInput {
|
|||
if (scheme.equalsIgnoreCase("xpipe")) {
|
||||
var action = uri.getAuthority();
|
||||
var args = Arrays.asList(uri.getPath().split("/"));
|
||||
|
||||
var a = switch (action.toLowerCase()) {
|
||||
case "add" -> new AddActionInput(args);
|
||||
default -> throw new IllegalStateException("Unexpected value: " + action);
|
||||
};
|
||||
|
||||
return List.of(a);
|
||||
var found = ActionProvider.ALL.stream()
|
||||
.filter(actionProvider -> actionProvider.getLauncherCallSite() != null
|
||||
&& actionProvider
|
||||
.getLauncherCallSite()
|
||||
.getId()
|
||||
.equalsIgnoreCase(action))
|
||||
.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) {
|
||||
|
@ -96,13 +94,11 @@ public abstract class LauncherInput {
|
|||
} catch (InvalidPathException ignored) {
|
||||
}
|
||||
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class LocalFileInput extends LauncherInput {
|
||||
public static class LocalFileInput implements ActionProvider.Action {
|
||||
|
||||
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
|
||||
private final List<String> args;
|
||||
|
@ -134,31 +130,4 @@ public abstract class LauncherInput {
|
|||
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.Setting;
|
||||
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.AppStyle;
|
||||
import io.xpipe.extension.event.ErrorEvent;
|
||||
|
@ -116,9 +116,9 @@ public class AppPrefs {
|
|||
// Automatically update
|
||||
// ====================
|
||||
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)
|
||||
.editable(AppDistributionType.get().supportsUpdate())
|
||||
.editable(XPipeDistributionType.get().supportsUpdate())
|
||||
.render(() -> new ToggleControl());
|
||||
private final BooleanProperty updateToPrereleases = 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) {
|
||||
var customHome = System.getenv("XPIPE_HOME");
|
||||
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"
|
||||
into "$distDir/base/app"
|
||||
}
|
||||
copy {
|
||||
from "$projectDir/logo/logo.ico"
|
||||
into "$distDir/base/app"
|
||||
}
|
||||
|
||||
file("$distDir/base/app/xpiped.exe").writable = true
|
||||
exec {
|
||||
|
@ -86,7 +90,12 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
|||
from "$distDir/jpackage/xpiped"
|
||||
into "$distDir/base/app"
|
||||
}
|
||||
copy {
|
||||
from "$projectDir/logo/logo.png"
|
||||
into "$distDir/base/"
|
||||
}
|
||||
|
||||
// Fixes a JPackage bug
|
||||
copy {
|
||||
from "$distDir/base/app/lib/app/xpiped.cfg"
|
||||
into "$distDir/base/app"
|
||||
|
@ -135,6 +144,10 @@ if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
|||
from "$distDir/jpackage/xpiped.app/Contents"
|
||||
into "$distDir/X-Pipe.app/Contents/"
|
||||
}
|
||||
copy {
|
||||
from "$projectDir/logo/logo.icns"
|
||||
into "$distDir/X-Pipe.app/Contents/Resources/"
|
||||
}
|
||||
copy {
|
||||
from "$distDir/cli/xpipe"
|
||||
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.actions.FileBrowseAction;
|
||||
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.actions.*;
|
||||
import io.xpipe.ext.base.apps.*;
|
||||
import io.xpipe.extension.DataSourceProvider;
|
||||
import io.xpipe.extension.DataStoreActionProvider;
|
||||
import io.xpipe.extension.DataStoreProvider;
|
||||
import io.xpipe.extension.DataSourceTarget;
|
||||
import io.xpipe.extension.util.ActionProvider;
|
||||
|
||||
open module 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 io.xpipe.app;
|
||||
|
||||
provides ActionProvider with AddStoreAction;
|
||||
provides DataStoreActionProvider with
|
||||
StreamExportAction,
|
||||
ShareStoreAction,
|
||||
|
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
||||
|
@ -37,7 +38,7 @@ public class XPipeServiceProviders {
|
|||
ModuleLayerLoader.loadAll(layer, hasDaemon);
|
||||
|
||||
if (hasDaemon) {
|
||||
DataStoreActionProvider.init(layer);
|
||||
ActionProvider.init(layer);
|
||||
DataSourceActionProvider.init(layer);
|
||||
ProxyFunction.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.extension.event.TrackEvent;
|
||||
|
||||
public interface AppDistributionType {
|
||||
public interface XPipeDistributionType {
|
||||
|
||||
AppDistributionType DEVELOPMENT = new AppDistributionType() {
|
||||
XPipeDistributionType DEVELOPMENT = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
|
@ -17,12 +19,18 @@ public interface AppDistributionType {
|
|||
TrackEvent.info("Development mode update executed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsURLs() {
|
||||
// Enabled for testing
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "development";
|
||||
}
|
||||
};
|
||||
AppDistributionType PORTABLE = new AppDistributionType() {
|
||||
XPipeDistributionType PORTABLE = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
|
@ -32,12 +40,17 @@ public interface AppDistributionType {
|
|||
@Override
|
||||
public void performUpdateAction() {}
|
||||
|
||||
@Override
|
||||
public boolean supportsURLs() {
|
||||
return OsType.getLocal().equals(OsType.MAC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "portable";
|
||||
}
|
||||
};
|
||||
AppDistributionType INSTALLATION = new AppDistributionType() {
|
||||
XPipeDistributionType INSTALLATION = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
|
@ -49,14 +62,19 @@ public interface AppDistributionType {
|
|||
TrackEvent.info("Update action called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsURLs() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "install";
|
||||
}
|
||||
};
|
||||
|
||||
static AppDistributionType get() {
|
||||
if (!AppProperties.get().isImage()) {
|
||||
static XPipeDistributionType get() {
|
||||
if (!ModuleHelper.isImage()) {
|
||||
return DEVELOPMENT;
|
||||
}
|
||||
|
||||
|
@ -71,5 +89,7 @@ public interface AppDistributionType {
|
|||
|
||||
void performUpdateAction();
|
||||
|
||||
boolean supportsURLs();
|
||||
|
||||
String getName();
|
||||
}
|
|
@ -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.util.ActionProvider;
|
||||
import io.xpipe.extension.util.ModuleLayerLoader;
|
||||
import io.xpipe.extension.util.XPipeDaemon;
|
||||
|
||||
|
@ -52,6 +53,7 @@ open module io.xpipe.extension {
|
|||
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;
|
||||
|
|
Loading…
Reference in a new issue