Final updater fixes

This commit is contained in:
crschnick 2023-04-11 21:20:09 +00:00
parent 86ec53ea56
commit d0117fb78e
27 changed files with 671 additions and 516 deletions

View file

@ -1,4 +1,4 @@
<img src="https://user-images.githubusercontent.com/72509152/213873342-7638e830-8a95-4b5d-ad3e-5a9a0b4bf538.png" alt="drawing" width="300"/>
<img src="https://user-images.githubusercontent.com/72509152/213873342-7638e830-8a95-4b5d-ad3e-5a9a0b4bf538.png" alt="drawing" width="250"/>
### A smart connection manager and remote file explorer

View file

@ -42,8 +42,11 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
private List<SideMenuBarComp.Entry> createEntryList() {
var l = new ArrayList<>(List.of(
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
new SideMenuBarComp.Entry(AppI18n.observable("browser"), "mdi2f-file-cabinet", new FileBrowserComp(FileBrowserModel.DEFAULT)),
//new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
new SideMenuBarComp.Entry(
AppI18n.observable("browser"),
"mdi2f-file-cabinet",
new FileBrowserComp(FileBrowserModel.DEFAULT)),
// new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
new SideMenuBarComp.Entry(
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
@ -51,9 +54,10 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
// new SideMenuBarComp.Entry(AppI18n.observable("account"), "mdi2a-account", new StorageLayoutComp()),
new SideMenuBarComp.Entry(AppI18n.observable("about"), "mdi2p-package-variant", new AboutTabComp())));
if (AppProperties.get().isDeveloperMode()) {
l.add(new SideMenuBarComp.Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new
DeveloperTabComp()));
l.add(new SideMenuBarComp.Entry(
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
}
// l.add(new SideMenuBarComp.Entry(AppI18n.observable("abc"), "mdi2b-book-open-variant", Comp.of(() -> {
// var fi = new FontIcon("mdsal-dvr");
// fi.setIconSize(30);

View file

@ -3,13 +3,11 @@ package io.xpipe.app.comp.about;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.util.XPipeDistributionType;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.update.XPipeDistributionType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@ -20,62 +18,51 @@ import org.kordamp.ikonli.javafx.FontIcon;
public class UpdateCheckComp extends SimpleComp {
private final ObservableBooleanValue updateAvailable;
private final ObservableValue<Boolean> updateReady;
public UpdateCheckComp() {
updateAvailable = Bindings.createBooleanBinding(
updateReady = PlatformThread.sync(Bindings.createBooleanBinding(
() -> {
return AppUpdater.get().getLastUpdateCheckResult().getValue() != null
&& AppUpdater.get()
.getLastUpdateCheckResult()
.getValue()
.isUpdate();
return XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null;
},
PlatformThread.sync(AppUpdater.get().getLastUpdateCheckResult()));
updateReady = Bindings.createBooleanBinding(
() -> {
return AppUpdater.get().getDownloadedUpdate().getValue() != null;
},
PlatformThread.sync(AppUpdater.get().getDownloadedUpdate()));
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate()));
}
private void restart() {
AppUpdater.get().refreshUpdateCheckSilent();
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
UpdateAvailableAlert.showIfNeeded();
}
private void download() {
AppUpdater.get().downloadUpdateAsync();
}
private void refresh() {
AppUpdater.get().checkForUpdateAsync();
ThreadHelper.runFailableAsync(() -> {
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
XPipeDistributionType.get().getUpdateHandler().prepareUpdate();
});
}
private ObservableValue<String> descriptionText() {
return PlatformThread.sync(Bindings.createStringBinding(
() -> {
if (AppUpdater.get().getDownloadedUpdate().getValue() != null) {
return AppI18n.get("updateRestart");
if (XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null) {
return null;
}
if (AppUpdater.get().getLastUpdateCheckResult().getValue() != null
&& AppUpdater.get()
if (XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult().getValue() != null
&& XPipeDistributionType.get().getUpdateHandler()
.getLastUpdateCheckResult()
.getValue()
.isUpdate()) {
return AppI18n.get(
"updateAvailable",
AppUpdater.get()
XPipeDistributionType.get().getUpdateHandler()
.getLastUpdateCheckResult()
.getValue()
.getVersion());
}
if (AppUpdater.get().getLastUpdateCheckResult().getValue() != null) {
if (XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult().getValue() != null) {
return AppI18n.readableDuration(
new SimpleObjectProperty<>(AppUpdater.get()
new SimpleObjectProperty<>(XPipeDistributionType.get().getUpdateHandler()
.getLastUpdateCheckResult()
.getValue()
.getCheckTime()),
@ -85,15 +72,15 @@ public class UpdateCheckComp extends SimpleComp {
return null;
}
},
AppUpdater.get().getLastUpdateCheckResult(),
AppUpdater.get().getDownloadedUpdate(),
AppUpdater.get().getBusy()));
XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult(),
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate(),
XPipeDistributionType.get().getUpdateHandler().getBusy()));
}
@Override
protected Region createSimple() {
var button = new Button();
button.disableProperty().bind(PlatformThread.sync(AppUpdater.get().getBusy()));
button.disableProperty().bind(PlatformThread.sync(XPipeDistributionType.get().getUpdateHandler().getBusy()));
button.textProperty()
.bind(Bindings.createStringBinding(
() -> {
@ -101,30 +88,18 @@ public class UpdateCheckComp extends SimpleComp {
return AppI18n.get("updateReady");
}
if (updateAvailable.getValue()) {
return XPipeDistributionType.get().supportsUpdate()
? AppI18n.get("downloadUpdate")
: AppI18n.get("checkOutUpdate");
} else {
return AppI18n.get("checkForUpdates");
}
return AppI18n.get("checkForUpdates");
},
updateAvailable,
updateReady));
button.graphicProperty()
.bind(Bindings.createObjectBinding(
() -> {
if (updateReady.getValue()) {
return new FontIcon("mdi2r-restart");
return new FontIcon("mdi2a-apple-airplay");
}
if (updateAvailable.getValue()) {
return new FontIcon("mdi2d-download");
} else {
return new FontIcon("mdi2r-refresh");
}
return new FontIcon("mdi2r-refresh");
},
updateAvailable,
updateReady));
button.getStyleClass().add("button-comp");
button.setOnAction(e -> {
@ -133,14 +108,7 @@ public class UpdateCheckComp extends SimpleComp {
return;
}
if (updateAvailable.getValue() && !XPipeDistributionType.get().supportsUpdate()) {
Hyperlinks.open(
AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl());
} else if (updateAvailable.getValue()) {
download();
} else {
refresh();
}
refresh();
});
var checked = new Label();

View file

@ -1,9 +1,14 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.app.update.XPipeDistributionType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
@ -40,6 +45,18 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
});
vbox.getChildren().add(b.createRegion());
});
{
// vbox.getChildren().add(new Spacer(Orientation.VERTICAL));
var fi = new FontIcon("mdi2u-update");
var b = new BigIconButton(AppI18n.observable("update"), fi, () -> UpdateAvailableAlert.showIfNeeded());
b.apply(GrowAugment.create(true, false));
b.hide(PlatformThread.sync(Bindings.createBooleanBinding(() -> {
return XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() == null;
}, XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate())));
vbox.getChildren().add(b.createRegion());
}
vbox.getStyleClass().add("sidebar-comp");
return new SimpleCompStructure<>(vbox);
}

View file

@ -5,7 +5,7 @@ import io.xpipe.app.comp.AppLayoutComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.core.process.OsType;
import javafx.application.Application;
import javafx.application.Platform;
@ -76,21 +76,17 @@ public class App extends Application {
() -> {
var base = String.format(
"X-Pipe Desktop (%s)", AppProperties.get().getVersion());
var suffix = AppUpdater.get().getLastUpdateCheckResult().getValue() != null
&& AppUpdater.get()
.getLastUpdateCheckResult()
.getValue()
.isUpdate()
var suffix = XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null
? String.format(
" (Update to %s available)",
AppUpdater.get()
.getLastUpdateCheckResult()
" (Update to %s ready)",
XPipeDistributionType.get().getUpdateHandler()
.getPreparedUpdate()
.getValue()
.getVersion())
: "";
return base + suffix;
},
AppUpdater.get().getLastUpdateCheckResult());
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate());
var appWindow = new AppMainWindow(stage);
appWindow.getStage().titleProperty().bind(PlatformThread.sync(titleBinding));

View file

@ -3,10 +3,12 @@ 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.issue.*;
import io.xpipe.app.issue.ErrorAction;
import io.xpipe.app.issue.ErrorHandler;
import io.xpipe.app.issue.LogErrorHandler;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.update.AppUpdater;
import io.xpipe.app.util.FileBridge;
import io.xpipe.core.util.JacksonMapper;
@ -40,7 +42,6 @@ public class BaseMode extends OperationMode {
AppFileWatcher.init();
FileBridge.init();
AppSocketServer.init();
AppUpdater.init();
TrackEvent.info("mode", "Finished base components initialization");
}

View file

@ -11,7 +11,7 @@ import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.launcher.LauncherCommand;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.app.util.XPipeSession;
import org.apache.commons.lang3.function.FailableRunnable;
import java.util.ArrayList;

View file

@ -68,7 +68,7 @@ public class ErrorDetailsComp extends SimpleComp {
// AppI18n.observable("events"), "mdi2c-clipboard-list-outline", createTrackEventHistory()));
// }
var tb = new TabPaneComp(new SimpleObjectProperty<>(items.get(0)), items);
var tb = new TabPaneComp(new SimpleObjectProperty<>(items.size() > 0 ? items.get(0) : null), items);
tb.apply(r -> AppFont.small(r.get()));
return tb.createRegion();
}

View file

@ -6,7 +6,7 @@ import io.sentry.protocol.User;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.XPipeDistributionType;
import io.xpipe.app.update.XPipeDistributionType;
import org.apache.commons.io.FileUtils;
import java.nio.file.Files;
@ -23,7 +23,7 @@ public class SentryErrorHandler {
options.setEnableUncaughtExceptionHandler(false);
options.setAttachServerName(false);
// options.setDebug(true);
options.setDist(XPipeDistributionType.get().getName());
options.setDist(XPipeDistributionType.get().getId());
options.setRelease(AppProperties.get().getVersion());
options.setEnableShutdownHook(false);
options.setProguardUuid(AppProperties.get().getBuildUuid().toString());

View file

@ -3,8 +3,8 @@ 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.update.AppUpdater;
import io.xpipe.app.util.Hyperlinks;
import io.xpipe.app.update.XPipeDistributionType;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
@ -87,8 +87,7 @@ public class TerminalErrorHandler implements ErrorHandler {
private static void handleProbableUpdate() {
try {
AppUpdater.init();
var rel = AppUpdater.get().refreshUpdateCheck();
var rel = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
if (rel != null && rel.isUpdate()) {
var update = AppWindowHelper.showBlockingAlert(alert -> {
alert.setAlertType(Alert.AlertType.INFORMATION);

View file

@ -14,7 +14,6 @@ import io.xpipe.app.ext.PrefsHandler;
import io.xpipe.app.ext.PrefsProvider;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.XPipeDistributionType;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
@ -146,13 +145,14 @@ public class AppPrefs {
// Automatically update
// ====================
private final BooleanProperty automaticallyUpdate =
typed(new SimpleBooleanProperty(XPipeDistributionType.get().supportsUpdate()), Boolean.class);
private final BooleanField automaticallyUpdateField =
BooleanField.ofBooleanType(automaticallyUpdate).render(() -> new CustomToggleControl());
private final BooleanProperty updateToPrereleases = typed(new SimpleBooleanProperty(false), Boolean.class);
private final BooleanField updateToPrereleasesField =
BooleanField.ofBooleanType(updateToPrereleases).render(() -> new CustomToggleControl());
private final BooleanProperty automaticallyCheckForUpdates =
typed(new SimpleBooleanProperty(true), Boolean.class);
private final BooleanField automaticallyCheckForUpdatesField =
BooleanField.ofBooleanType(automaticallyCheckForUpdates).render(() -> new CustomToggleControl());
private final BooleanProperty checkForPrereleases = typed(new SimpleBooleanProperty(false), Boolean.class);
private final BooleanField checkForPrereleasesField =
BooleanField.ofBooleanType(checkForPrereleases).render(() -> new CustomToggleControl());
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
@ -236,11 +236,11 @@ public class AppPrefs {
}
public ReadOnlyBooleanProperty automaticallyUpdate() {
return automaticallyUpdate;
return automaticallyCheckForUpdates;
}
public ReadOnlyBooleanProperty updateToPrereleases() {
return updateToPrereleases;
return checkForPrereleases;
}
public ReadOnlyBooleanProperty confirmDeletions() {
@ -431,12 +431,8 @@ public class AppPrefs {
Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)),
Group.of(
"updates",
Setting.of("automaticallyUpdate", automaticallyUpdateField, automaticallyUpdate)
.applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty(
XPipeDistributionType.get().supportsUpdate()))),
Setting.of("updateToPrereleases", updateToPrereleasesField, updateToPrereleases)
.applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty(
XPipeDistributionType.get().supportsUpdate())))),
Setting.of("automaticallyUpdate", automaticallyCheckForUpdatesField, automaticallyCheckForUpdates),
Setting.of("updateToPrereleases", checkForPrereleasesField, checkForPrereleases)),
Group.of(
"advanced",
Setting.of("storageDirectory", storageDirectoryControl, internalStorageDirectory),

View file

@ -8,6 +8,7 @@ import io.xpipe.core.impl.FileNames;
import io.xpipe.core.impl.LocalStore;
import io.xpipe.core.process.OsType;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import lombok.Getter;
import java.util.List;
@ -64,7 +65,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
new SimpleType("gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
ApplicationHelper.checkSupport(pc, executable, getDisplayName());
@ -144,7 +145,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
.orElse(null);
}
public abstract void launch(String name, String file) throws Exception;
public abstract void launch(String name, String file, boolean elevated) throws Exception;
static class MacOsTerminalType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
@ -153,7 +154,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
var suffix = file.equals(pc.getShellDialect().getOpenCommand())
? "\"\""
@ -171,7 +172,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
var custom = AppPrefs.get().customTerminalCommand().getValue();
if (custom == null || custom.isBlank()) {
throw new IllegalStateException("No custom terminal command specified");
@ -207,7 +208,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
try (ShellControl pc = LocalStore.getShell()) {
var cmd = String.format(
"""
@ -241,7 +242,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
if (!MacOsPermissions.waitForAccessibilityPermissions()) {
return;
}
@ -279,7 +280,18 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
}
@Override
public void launch(String name, String file) throws Exception {
public void launch(String name, String file, boolean elevated) throws Exception {
if (elevated) {
if (OsType.getLocal().equals(OsType.WINDOWS)) {
try (ShellControl pc = LocalStore.getShell().subShell(ShellDialects.POWERSHELL).start()) {
ApplicationHelper.checkSupport(pc, executable, displayName);
var toExecute = "Start-Process \"" + executable + "\" -Verb RunAs -ArgumentList \"" + toCommand(name, file).replaceAll("\"", "`\"") + "\"";
pc.executeSimpleCommand(toExecute);
}
return;
}
}
try (ShellControl pc = LocalStore.getShell()) {
ApplicationHelper.checkSupport(pc, executable, displayName);

View file

@ -2,7 +2,7 @@ package io.xpipe.app.storage;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.app.util.XPipeSession;
import lombok.NonNull;
import org.apache.commons.io.FileUtils;

View file

@ -6,7 +6,7 @@ import io.xpipe.beacon.BeaconDaemonController;
import io.xpipe.core.store.DataStore;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.app.util.XPipeSession;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;

View file

@ -2,7 +2,7 @@ package io.xpipe.app.test;
import io.xpipe.app.ext.XPipeServiceProviders;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.XPipeSession;
import io.xpipe.app.util.XPipeSession;
import org.junit.jupiter.api.BeforeAll;
import java.util.UUID;

View file

@ -1,308 +0,0 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.app.util.XPipeDistributionType;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import lombok.extern.jackson.Jacksonized;
import org.kohsuke.github.GHRelease;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
@Getter
public class AppUpdater {
private static AppUpdater INSTANCE;
private final Property<AvailableRelease> lastUpdateCheckResult = new SimpleObjectProperty<>();
private final Property<DownloadedUpdate> downloadedUpdate = new SimpleObjectProperty<>();
private final BooleanProperty busy = new SimpleBooleanProperty();
private final PerformedUpdate performedUpdate;
private final boolean updateSucceeded;
private AppUpdater() {
performedUpdate = AppCache.get("performedUpdate", PerformedUpdate.class, () -> null);
var hasUpdated = performedUpdate != null;
event("Was updated is " + hasUpdated);
if (hasUpdated) {
AppCache.clear("performedUpdate");
updateSucceeded = AppProperties.get().getVersion().equals(performedUpdate.getNewVersion());
AppCache.clear("lastUpdateCheckResult");
AppCache.clear("downloadedUpdate");
event("Found information about recent update");
} else {
updateSucceeded = false;
}
downloadedUpdate.setValue(AppCache.get("downloadedUpdate", DownloadedUpdate.class, () -> null));
// Check if the original version this was downloaded from is still the same
if (downloadedUpdate.getValue() != null
&& !downloadedUpdate
.getValue()
.getSourceVersion()
.equals(AppProperties.get().getVersion())) {
downloadedUpdate.setValue(null);
}
// Check if somehow the downloaded version is equal to the current one
if (downloadedUpdate.getValue() != null
&& downloadedUpdate
.getValue()
.getVersion()
.equals(AppProperties.get().getVersion())) {
downloadedUpdate.setValue(null);
}
if (!XPipeDistributionType.get().supportsUpdate()) {
downloadedUpdate.setValue(null);
}
downloadedUpdate.addListener((c, o, n) -> {
AppCache.update("downloadedUpdate", n);
});
lastUpdateCheckResult.addListener((c, o, n) -> {
if (n != null && downloadedUpdate.getValue() != null && n.isUpdate() && n.getVersion().equals(downloadedUpdate.getValue().getVersion())) {
return;
}
downloadedUpdate.setValue(null);
});
if (XPipeDistributionType.get().checkForUpdateOnStartup()) {
refreshUpdateCheckSilent();
}
}
private static void event(String msg) {
TrackEvent.builder().category("installer").type("info").message(msg).handle();
}
public static AppUpdater get() {
return INSTANCE;
}
public static void init() {
if (INSTANCE != null) {
return;
}
INSTANCE = new AppUpdater();
startBackgroundUpdater();
}
private static void startBackgroundUpdater() {
if (XPipeDistributionType.get().supportsUpdate()
&& XPipeDistributionType.get() != XPipeDistributionType.DEVELOPMENT) {
ThreadHelper.create("updater", true, () -> {
ThreadHelper.sleep(Duration.ofMinutes(10).toMillis());
event("Starting background updater thread");
while (true) {
var rel = INSTANCE.refreshUpdateCheckSilent();
if (rel != null
&& AppPrefs.get().automaticallyUpdate().get() && rel.isUpdate()) {
event("Performing background update");
INSTANCE.downloadUpdate();
}
ThreadHelper.sleep(Duration.ofHours(1).toMillis());
}
})
.start();
}
}
private static boolean isUpdate(String releaseVersion) {
if (AppPrefs.get() != null
&& AppPrefs.get().developerMode().getValue()
&& AppPrefs.get().developerDisableUpdateVersionCheck().get()) {
event("Bypassing version check");
return true;
}
if (!AppProperties.get().getVersion().equals(releaseVersion)) {
event("Release has a different version");
return true;
}
return false;
}
public void downloadUpdateAsync() {
ThreadHelper.runAsync(() -> downloadUpdate());
}
public synchronized void downloadUpdate() {
if (busy.getValue()) {
return;
}
if (lastUpdateCheckResult.getValue() == null) {
return;
}
if (!XPipeDistributionType.get().supportsUpdate()) {
return;
}
if (!lastUpdateCheckResult.getValue().isUpdate()) {
return;
}
try (var ignored = new BusyProperty(busy)) {
event("Performing update download ...");
try {
var downloadFile = AppDownloads.downloadInstaller(
lastUpdateCheckResult.getValue().getAssetType(),
lastUpdateCheckResult.getValue().version,
false);
if (downloadFile.isEmpty()) {
return;
}
var changelogString = AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().version, false);
var changelog = changelogString.orElse(null);
var rel = new DownloadedUpdate(
AppProperties.get().getVersion(),
lastUpdateCheckResult.getValue().version,
downloadFile.get(),
changelog,
lastUpdateCheckResult.getValue().getAssetType());
downloadedUpdate.setValue(rel);
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omit().handle();
}
}
}
public void executeUpdateAndClose() {
if (busy.getValue()) {
return;
}
if (downloadedUpdate.getValue() == null) {
return;
}
var downloadFile = downloadedUpdate.getValue().getFile();
if (!Files.exists(downloadFile)) {
return;
}
event("Executing update ...");
OperationMode.executeAfterShutdown(() -> {
try {
AppInstaller.installFileLocal(downloadedUpdate.getValue().getAssetType(), downloadFile);
} catch (Throwable ex) {
ex.printStackTrace();
} finally {
var performedUpdate = new PerformedUpdate(
downloadedUpdate.getValue().getVersion(),
downloadedUpdate.getValue().getBody(),
downloadedUpdate.getValue().getVersion());
AppCache.update("performedUpdate", performedUpdate);
}
});
}
public void checkForUpdateAsync() {
ThreadHelper.runAsync(() -> refreshUpdateCheckSilent());
}
public synchronized AvailableRelease refreshUpdateCheckSilent() {
try {
return refreshUpdateCheck();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omit().handle();
return null;
}
}
public synchronized AvailableRelease refreshUpdateCheck() throws IOException {
if (busy.getValue()) {
return lastUpdateCheckResult.getValue();
}
try (var ignored = new BusyProperty(busy)) {
var rel = AppDownloads.getLatestSuitableRelease();
event("Determined latest suitable release "
+ rel.map(GHRelease::getName).orElse(null));
if (rel.isEmpty()) {
lastUpdateCheckResult.setValue(null);
return null;
}
var isUpdate = isUpdate(rel.get().getTagName());
var assetType = AppInstaller.getSuitablePlatformAsset();
var ghAsset = rel.orElseThrow().listAssets().toList().stream()
.filter(g -> assetType.isCorrectAsset(g.getName()))
.findAny();
if (ghAsset.isEmpty()) {
return null;
}
event("Selected asset " + ghAsset.get().getName());
lastUpdateCheckResult.setValue(new AvailableRelease(
AppProperties.get().getVersion(),
rel.get().getTagName(),
rel.get().getHtmlUrl().toString(),
ghAsset.get().getBrowserDownloadUrl(),
assetType,
Instant.now(),
isUpdate));
}
return lastUpdateCheckResult.getValue();
}
@Value
@Builder
@Jacksonized
public static class PerformedUpdate {
String name;
String rawDescription;
String newVersion;
}
@Value
@Builder
@Jacksonized
@With
public static class AvailableRelease {
String sourceVersion;
String version;
String releaseUrl;
String downloadUrl;
AppInstaller.InstallerAssetType assetType;
Instant checkTime;
boolean isUpdate;
}
@Value
@Builder
@Jacksonized
public static class DownloadedUpdate {
String sourceVersion;
String version;
Path file;
String body;
AppInstaller.InstallerAssetType assetType;
}
}

View file

@ -0,0 +1,50 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.fxcomps.impl.CodeSnippet;
import io.xpipe.app.fxcomps.impl.CodeSnippetComp;
import io.xpipe.core.store.ShellStore;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.layout.Region;
import java.time.Instant;
public class ChocoUpdater extends UpdateHandler {
public ChocoUpdater() {
super(true);
}
@Override
public Region createInterface() {
var snippet = CodeSnippet.builder()
.keyword("choco")
.space()
.identifier("install")
.space()
.string("xpipe")
.space()
.keyword("--version=" + getPreparedUpdate().getValue().getVersion())
.build();
return new CodeSnippetComp(false, new SimpleObjectProperty<>(snippet)).createRegion();
}
public AvailableRelease refreshUpdateCheckImpl() throws Exception {
try (var sc = ShellStore.createLocal().create().start()) {
var latest = sc.executeStringSimpleCommand(
"choco outdated -r --nocolor").lines().filter(s -> s.startsWith("xpipe")).findAny().orElseThrow().split("\\|")[2];
var isUpdate = isUpdate(latest);
var rel = new AvailableRelease(
AppProperties.get().getVersion(),
XPipeDistributionType.get().getId(),
latest,
"https://community.chocolatey.org/packages/xpipe/" + latest,
null,
null,
Instant.now(),
isUpdate);
lastUpdateCheckResult.setValue(rel);
return lastUpdateCheckResult.getValue();
}
}
}

View file

@ -0,0 +1,84 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppProperties;
import javafx.scene.layout.Region;
import org.kohsuke.github.GHRelease;
import java.nio.file.Files;
import java.time.Instant;
public class GitHubUpdater extends UpdateHandler {
public GitHubUpdater(boolean startBackgroundThread) {
super(startBackgroundThread);
}
@Override
public Region createInterface() {
return null;
}
public void prepareUpdateImpl() {
var downloadFile = AppDownloads.downloadInstaller(
lastUpdateCheckResult.getValue().getAssetType(),
lastUpdateCheckResult.getValue().getVersion(),
false);
if (downloadFile.isEmpty()) {
return;
}
var changelogString = AppDownloads.downloadChangelog(
lastUpdateCheckResult.getValue().getVersion(), false);
var changelog = changelogString.orElse(null);
var rel = new PreparedUpdate(
AppProperties.get().getVersion(),
XPipeDistributionType.get().getId(),
lastUpdateCheckResult.getValue().getVersion(),
lastUpdateCheckResult.getValue().getReleaseUrl(),
downloadFile.get(),
changelog,
lastUpdateCheckResult.getValue().getAssetType());
preparedUpdate.setValue(rel);
}
public void executeUpdateAndCloseImpl() throws Exception {
var downloadFile = preparedUpdate.getValue().getFile();
if (!Files.exists(downloadFile)) {
return;
}
AppInstaller.installFileLocal(preparedUpdate.getValue().getAssetType(), downloadFile);
}
public synchronized AvailableRelease refreshUpdateCheckImpl() throws Exception {
var rel = AppDownloads.getLatestSuitableRelease();
event("Determined latest suitable release "
+ rel.map(GHRelease::getName).orElse(null));
if (rel.isEmpty()) {
lastUpdateCheckResult.setValue(null);
return null;
}
var isUpdate = isUpdate(rel.get().getTagName());
var assetType = AppInstaller.getSuitablePlatformAsset();
var ghAsset = rel.orElseThrow().listAssets().toList().stream()
.filter(g -> assetType.isCorrectAsset(g.getName()))
.findAny();
if (ghAsset.isEmpty()) {
return null;
}
event("Selected asset " + ghAsset.get().getName());
lastUpdateCheckResult.setValue(new AvailableRelease(
AppProperties.get().getVersion(),
XPipeDistributionType.get().getId(),
rel.get().getTagName(),
rel.get().getHtmlUrl().toString(),
ghAsset.get().getBrowserDownloadUrl(),
assetType,
Instant.now(),
isUpdate));
return lastUpdateCheckResult.getValue();
}
}

View file

@ -0,0 +1,55 @@
package io.xpipe.app.update;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.util.Hyperlinks;
import javafx.scene.layout.Region;
import org.kohsuke.github.GHRelease;
import java.time.Instant;
public class PortableUpdater extends UpdateHandler {
public PortableUpdater() {
super(true);
}
@Override
public Region createInterface() {
return new ButtonComp(AppI18n.observable("checkOutUpdate"), () -> {
Hyperlinks.open(XPipeDistributionType.get()
.getUpdateHandler()
.getPreparedUpdate()
.getValue()
.getReleaseUrl());
}).createRegion();
}
public void executeUpdateAndCloseImpl() throws Exception {
throw new UnsupportedOperationException();
}
public synchronized AvailableRelease refreshUpdateCheckImpl() throws Exception {
var rel = AppDownloads.getLatestSuitableRelease();
event("Determined latest suitable release "
+ rel.map(GHRelease::getName).orElse(null));
if (rel.isEmpty()) {
lastUpdateCheckResult.setValue(null);
return null;
}
var isUpdate = isUpdate(rel.get().getTagName());
lastUpdateCheckResult.setValue(new AvailableRelease(
AppProperties.get().getVersion(),
XPipeDistributionType.get().getId(),
rel.get().getTagName(),
rel.get().getHtmlUrl().toString(),
null,
null,
Instant.now(),
isUpdate));
return lastUpdateCheckResult.getValue();
}
}

View file

@ -3,42 +3,51 @@ package io.xpipe.app.update;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
public class UpdateAvailableAlert {
public static void showIfNeeded() {
if (AppUpdater.get().getDownloadedUpdate().getValue() == null) {
UpdateHandler uh = XPipeDistributionType.get().getUpdateHandler();
if (uh.getPreparedUpdate().getValue() == null) {
return;
}
var u = AppUpdater.get().getDownloadedUpdate().getValue();
var u = uh.getPreparedUpdate().getValue();
var update = AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("updateReadyAlertTitle"));
alert.setAlertType(Alert.AlertType.NONE);
if (u.getBody() != null && !u.getBody().isBlank()) {
var markdown = new MarkdownComp(u.getBody(), s -> {
var header = "<h1>" + AppI18n.get("whatsNew", u.getVersion()) + "</h1>";
return header + s;
})
.createRegion();
alert.getDialogPane().setContent(markdown);
var markdown = new MarkdownComp(u.getBody() != null ? u.getBody() : "", s -> {
var header = "<h1>" + AppI18n.get("whatsNew", u.getVersion()) + "</h1>";
return header + s;
})
.createRegion();
alert.getButtonTypes().clear();
var updaterContent = uh.createInterface();
if (updaterContent != null) {
var stack = new StackPane(updaterContent);
stack.setPadding(new Insets(18));
var box = new VBox(markdown, stack);
box.setFillWidth(true);
box.setPadding(Insets.EMPTY);
alert.getDialogPane().setContent(box);
} else {
alert.getDialogPane()
.setContent(AppWindowHelper.alertContentText(AppI18n.get("updateReadyAlertContent")));
alert.getDialogPane().setContent(markdown);
alert.getButtonTypes().add(new ButtonType(AppI18n.get("install"), ButtonBar.ButtonData.OK_DONE));
}
alert.getButtonTypes().clear();
alert.getButtonTypes().add(new ButtonType(AppI18n.get("install"), ButtonBar.ButtonData.OK_DONE));
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ignore"), ButtonBar.ButtonData.NO));
})
.map(buttonType -> buttonType.getButtonData().isDefaultButton())
.orElse(false);
if (update) {
AppUpdater.get().executeUpdateAndClose();
uh.executeUpdateAndClose();
}
}
}

View file

@ -12,9 +12,8 @@ import javafx.stage.Modality;
public class UpdateChangelogAlert {
public static void showIfNeeded() {
var update = AppUpdater.get().getPerformedUpdate();
if (update != null && !AppUpdater.get().isUpdateSucceeded()) {
var update = XPipeDistributionType.get().getUpdateHandler().getPerformedUpdate();
if (update != null && !XPipeDistributionType.get().getUpdateHandler().isUpdateSucceeded()) {
ErrorEvent.fromMessage("Update did not succeed").handle();
return;
}

View file

@ -0,0 +1,264 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.BusyProperty;
import io.xpipe.app.util.ThreadHelper;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.layout.Region;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import lombok.With;
import lombok.extern.jackson.Jacksonized;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
@Getter
public abstract class UpdateHandler {
protected final Property<AvailableRelease> lastUpdateCheckResult = new SimpleObjectProperty<>();
protected final Property<PreparedUpdate> preparedUpdate = new SimpleObjectProperty<>();
protected final BooleanProperty busy = new SimpleBooleanProperty();
protected final PerformedUpdate performedUpdate;
protected final boolean updateSucceeded;
protected UpdateHandler(boolean startBackgroundThread) {
performedUpdate = AppCache.get("performedUpdate", PerformedUpdate.class, () -> null);
var hasUpdated = performedUpdate != null;
event("Was updated is " + hasUpdated);
if (hasUpdated) {
AppCache.clear("performedUpdate");
updateSucceeded = AppProperties.get().getVersion().equals(performedUpdate.getNewVersion());
AppCache.clear("preparedUpdate");
event("Found information about recent update");
} else {
updateSucceeded = false;
}
preparedUpdate.setValue(AppCache.get("preparedUpdate", PreparedUpdate.class, () -> null));
// Check if the original version this was downloaded from is still the same
if (preparedUpdate.getValue() != null
&& (!preparedUpdate
.getValue()
.getSourceVersion()
.equals(AppProperties.get().getVersion())
|| !XPipeDistributionType.get()
.getId()
.equals(preparedUpdate.getValue().getSourceDist()))) {
preparedUpdate.setValue(null);
}
// Check if somehow the downloaded version is equal to the current one
if (preparedUpdate.getValue() != null
&& preparedUpdate
.getValue()
.getVersion()
.equals(AppProperties.get().getVersion())) {
preparedUpdate.setValue(null);
}
preparedUpdate.addListener((c, o, n) -> {
AppCache.update("preparedUpdate", n);
});
lastUpdateCheckResult.addListener((c, o, n) -> {
if (n != null
&& preparedUpdate.getValue() != null
&& n.isUpdate()
&& n.getVersion().equals(preparedUpdate.getValue().getVersion())) {
return;
}
preparedUpdate.setValue(null);
});
if (startBackgroundThread) {
startBackgroundUpdater();
}
}
private void startBackgroundUpdater() {
ThreadHelper.create("updater", true, () -> {
ThreadHelper.sleep(Duration.ofMinutes(5).toMillis());
event("Starting background updater thread");
while (true) {
if (AppPrefs.get().automaticallyUpdate().get()) {
event("Performing background update");
refreshUpdateCheckSilent();
prepareUpdate();
}
ThreadHelper.sleep(Duration.ofHours(1).toMillis());
}
})
.start();
}
protected void event(String msg) {
TrackEvent.builder().category("updater").type("info").message(msg).handle();
}
protected final boolean isUpdate(String releaseVersion) {
if (AppPrefs.get() != null
&& AppPrefs.get().developerMode().getValue()
&& AppPrefs.get().developerDisableUpdateVersionCheck().get()) {
event("Bypassing version check");
return true;
}
if (!AppProperties.get().getVersion().equals(releaseVersion)) {
event("Release has a different version");
return true;
}
return false;
}
public final void prepareUpdateAsync() {
ThreadHelper.runAsync(() -> prepareUpdate());
}
public final void refreshUpdateCheckAsync() {
ThreadHelper.runAsync(() -> refreshUpdateCheckSilent());
}
public final AvailableRelease refreshUpdateCheckSilent() {
try {
return refreshUpdateCheck();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).omit().handle();
return null;
}
}
public final void prepareUpdate() {
if (busy.getValue()) {
return;
}
if (lastUpdateCheckResult.getValue() == null) {
return;
}
if (!lastUpdateCheckResult.getValue().isUpdate()) {
return;
}
try (var ignored = new BusyProperty(busy)) {
event("Performing update download ...");
prepareUpdateImpl();
}
}
public abstract Region createInterface();
public void prepareUpdateImpl() {
var changelogString =
AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().getVersion(), false);
var changelog = changelogString.orElse(null);
var rel = new PreparedUpdate(
AppProperties.get().getVersion(),
XPipeDistributionType.get().getId(),
lastUpdateCheckResult.getValue().getVersion(),
lastUpdateCheckResult.getValue().getReleaseUrl(),
null,
changelog,
lastUpdateCheckResult.getValue().getAssetType());
preparedUpdate.setValue(rel);
}
public final void executeUpdateAndClose() {
if (busy.getValue()) {
return;
}
if (preparedUpdate.getValue() == null) {
return;
}
var downloadFile = preparedUpdate.getValue().getFile();
if (!Files.exists(downloadFile)) {
return;
}
event("Executing update ...");
OperationMode.executeAfterShutdown(() -> {
try {
executeUpdateAndCloseImpl();
} catch (Throwable ex) {
ex.printStackTrace();
} finally {
var performedUpdate = new PerformedUpdate(
preparedUpdate.getValue().getVersion(),
preparedUpdate.getValue().getBody(),
preparedUpdate.getValue().getVersion());
AppCache.update("performedUpdate", performedUpdate);
}
});
}
public void executeUpdateAndCloseImpl() throws Exception {
throw new UnsupportedOperationException();
}
public final AvailableRelease refreshUpdateCheck() throws Exception {
if (busy.getValue()) {
return lastUpdateCheckResult.getValue();
}
try (var ignored = new BusyProperty(busy)) {
return refreshUpdateCheckImpl();
}
}
public abstract AvailableRelease refreshUpdateCheckImpl() throws Exception;
@Value
@Builder
@Jacksonized
public static class PerformedUpdate {
String name;
String rawDescription;
String newVersion;
}
@Value
@Builder
@Jacksonized
@With
public static class AvailableRelease {
String sourceVersion;
String sourceDist;
String version;
String releaseUrl;
String downloadUrl;
AppInstaller.InstallerAssetType assetType;
Instant checkTime;
boolean isUpdate;
}
@Value
@Builder
@Jacksonized
public static class PreparedUpdate {
String sourceVersion;
String sourceDist;
String version;
String releaseUrl;
Path file;
String body;
AppInstaller.InstallerAssetType assetType;
}
}

View file

@ -0,0 +1,77 @@
package io.xpipe.app.update;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.impl.LocalStore;
import lombok.Getter;
import java.util.Arrays;
import java.util.function.Supplier;
public enum XPipeDistributionType {
DEVELOPMENT("development", () -> new GitHubUpdater(false)),
PORTABLE("portable", () -> new PortableUpdater()),
INSTALLATION("install", () -> new GitHubUpdater(true)),
CHOCO("choco", () -> new ChocoUpdater());
private static XPipeDistributionType type;
XPipeDistributionType(String id, Supplier<UpdateHandler> updateHandlerSupplier) {
this.id = id;
this.updateHandlerSupplier = updateHandlerSupplier;
}
public static XPipeDistributionType get() {
if (type != null) {
return type;
}
if (!XPipeSession.get().isNewBuildSession()) {
var cached = AppCache.get("dist", String.class, () -> null);
var cachedType = Arrays.stream(values())
.filter(xPipeDistributionType ->
xPipeDistributionType.getId().equals(cached))
.findAny()
.orElse(null);
if (cachedType != null) {
return (type = cachedType);
}
}
type = determine();
AppCache.update("dist", type.getId());
return type;
}
public static XPipeDistributionType determine() {
try (var sc = LocalStore.getShell()) {
try (var chocoOut = sc.command("choco search --local-only -r xpipe").start()) {
var out = chocoOut.readStdoutDiscardErr();
if (chocoOut.getExitCode() == 0) {
var split = out.split("\\|");
if (split.length == 2) {
return CHOCO;
}
}
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
return XPipeDistributionType.INSTALLATION;
}
@Getter
private final String id;
private UpdateHandler updateHandler;
private final Supplier<UpdateHandler> updateHandlerSupplier;
public UpdateHandler getUpdateHandler() {
if (updateHandler == null) {
updateHandler = updateHandlerSupplier.get();
}
return updateHandler;
}
}

View file

@ -21,6 +21,6 @@ public class TerminalHelper {
throw new IllegalStateException(AppI18n.get("noTerminalSet"));
}
type.launch(title, command);
type.launch(title, command, false);
}
}

View file

@ -1,77 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.core.util.ModuleHelper;
import io.xpipe.core.util.XPipeInstallation;
public interface XPipeDistributionType {
XPipeDistributionType DEVELOPMENT = new XPipeDistributionType() {
@Override
public boolean checkForUpdateOnStartup() {
return false;
}
@Override
public boolean supportsUpdate() {
return true;
}
@Override
public String getName() {
return "development";
}
};
XPipeDistributionType PORTABLE = new XPipeDistributionType() {
@Override
public boolean checkForUpdateOnStartup() {
return false;
}
@Override
public boolean supportsUpdate() {
return false;
}
@Override
public String getName() {
return "portable";
}
};
XPipeDistributionType INSTALLATION = new XPipeDistributionType() {
@Override
public boolean checkForUpdateOnStartup() {
return true;
}
@Override
public boolean supportsUpdate() {
return true;
}
@Override
public String getName() {
return "install";
}
};
static XPipeDistributionType get() {
if (!ModuleHelper.isImage()) {
return DEVELOPMENT;
}
if (XPipeInstallation.isInstallationDistribution()) {
return INSTALLATION;
} else {
return PORTABLE;
}
}
boolean checkForUpdateOnStartup();
boolean supportsUpdate();
String getName();
}

View file

@ -1,6 +1,9 @@
package io.xpipe.core.util;
package io.xpipe.app.util;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.core.process.OsType;
import io.xpipe.core.util.UuidHelper;
import lombok.Value;
import java.nio.file.Files;
@ -13,6 +16,8 @@ public class XPipeSession {
boolean isNewSystemSession;
boolean isNewBuildSession;
/**
* Unique identifier that resets on every X-Pipe restart.
*/
@ -61,7 +66,11 @@ public class XPipeSession {
} catch (Exception ignored) {
}
INSTANCE = new XPipeSession(isNewSystemSession, UUID.randomUUID(), buildSessionId, systemSessionId);
var s = AppCache.get("lastBuild", String.class, () -> buildSessionId.toString());
var isBuildChanged = !buildSessionId.toString().equals(s);
AppCache.update("lastBuild", AppProperties.get().getVersion());
INSTANCE = new XPipeSession(isNewSystemSession, isBuildChanged, UUID.randomUUID(), buildSessionId, systemSessionId);
}
public static XPipeSession get() {

View file

@ -16,7 +16,7 @@ saveWindowLocation=Save window location
saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts.
startupShutdown=Startup / Shutdown
system=System
updateToPrereleases=Update to prereleases
updateToPrereleases=Include prereleases
updateToPrereleasesDescription=When enabled, the update check will also look for available prereleases in addition to full releases.
storage=Storage
runOnStartup=Run on startup
@ -45,8 +45,8 @@ cancel=Cancel
notAnAbsolutePath=Not an absolute path
notADirectory=Not a directory
notAnEmptyDirectory=Not an empty directory
automaticallyUpdate=Automatically update
automaticallyUpdateDescription=When enabled, new releases are automatically downloaded in the background while X-Pipe is running and installed on the next launch.
automaticallyUpdate=Check for updates
automaticallyUpdateDescription=When enabled, new releases are automatically fetched in the background while X-Pipe is running.
sendAnonymousErrorReports=Send anonymous error reports
sendUsageStatistics=Send anonymous usage statistics
storageDirectory=Storage directory