mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-26 01:20:28 +00:00
More work on custom updating methods
This commit is contained in:
parent
86ec53ea56
commit
8c549b8050
26 changed files with 695 additions and 514 deletions
|
@ -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
|
### A smart connection manager and remote file explorer
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,11 @@ public class AppLayoutComp extends Comp<CompStructure<BorderPane>> {
|
||||||
private List<SideMenuBarComp.Entry> createEntryList() {
|
private List<SideMenuBarComp.Entry> createEntryList() {
|
||||||
var l = new ArrayList<>(List.of(
|
var l = new ArrayList<>(List.of(
|
||||||
new SideMenuBarComp.Entry(AppI18n.observable("connections"), "mdi2c-connection", new StoreLayoutComp()),
|
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(
|
||||||
//new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
|
AppI18n.observable("browser"),
|
||||||
|
"mdi2f-file-cabinet",
|
||||||
|
new FileBrowserComp(FileBrowserModel.DEFAULT)),
|
||||||
|
// new SideMenuBarComp.Entry(AppI18n.observable("data"), "mdsal-dvr", new SourceCollectionLayoutComp()),
|
||||||
new SideMenuBarComp.Entry(
|
new SideMenuBarComp.Entry(
|
||||||
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),
|
AppI18n.observable("settings"), "mdsmz-miscellaneous_services", new PrefsComp(this)),
|
||||||
// new SideMenuBarComp.Entry(AppI18n.observable("help"), "mdi2b-book-open-variant", new
|
// 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("account"), "mdi2a-account", new StorageLayoutComp()),
|
||||||
new SideMenuBarComp.Entry(AppI18n.observable("about"), "mdi2p-package-variant", new AboutTabComp())));
|
new SideMenuBarComp.Entry(AppI18n.observable("about"), "mdi2p-package-variant", new AboutTabComp())));
|
||||||
if (AppProperties.get().isDeveloperMode()) {
|
if (AppProperties.get().isDeveloperMode()) {
|
||||||
l.add(new SideMenuBarComp.Entry(AppI18n.observable("developer"), "mdi2b-book-open-variant", new
|
l.add(new SideMenuBarComp.Entry(
|
||||||
DeveloperTabComp()));
|
AppI18n.observable("developer"), "mdi2b-book-open-variant", new DeveloperTabComp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// l.add(new SideMenuBarComp.Entry(AppI18n.observable("abc"), "mdi2b-book-open-variant", Comp.of(() -> {
|
// l.add(new SideMenuBarComp.Entry(AppI18n.observable("abc"), "mdi2b-book-open-variant", Comp.of(() -> {
|
||||||
// var fi = new FontIcon("mdsal-dvr");
|
// var fi = new FontIcon("mdsal-dvr");
|
||||||
// fi.setIconSize(30);
|
// fi.setIconSize(30);
|
||||||
|
|
|
@ -3,13 +3,11 @@ package io.xpipe.app.comp.about;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.SimpleComp;
|
import io.xpipe.app.fxcomps.SimpleComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.update.AppUpdater;
|
|
||||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.app.util.XPipeDistributionType;
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableBooleanValue;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -20,62 +18,51 @@ import org.kordamp.ikonli.javafx.FontIcon;
|
||||||
|
|
||||||
public class UpdateCheckComp extends SimpleComp {
|
public class UpdateCheckComp extends SimpleComp {
|
||||||
|
|
||||||
private final ObservableBooleanValue updateAvailable;
|
|
||||||
private final ObservableValue<Boolean> updateReady;
|
private final ObservableValue<Boolean> updateReady;
|
||||||
|
|
||||||
public UpdateCheckComp() {
|
public UpdateCheckComp() {
|
||||||
updateAvailable = Bindings.createBooleanBinding(
|
updateReady = PlatformThread.sync(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
return AppUpdater.get().getLastUpdateCheckResult().getValue() != null
|
return XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null;
|
||||||
&& AppUpdater.get()
|
|
||||||
.getLastUpdateCheckResult()
|
|
||||||
.getValue()
|
|
||||||
.isUpdate();
|
|
||||||
},
|
},
|
||||||
PlatformThread.sync(AppUpdater.get().getLastUpdateCheckResult()));
|
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate()));
|
||||||
updateReady = Bindings.createBooleanBinding(
|
|
||||||
() -> {
|
|
||||||
return AppUpdater.get().getDownloadedUpdate().getValue() != null;
|
|
||||||
},
|
|
||||||
PlatformThread.sync(AppUpdater.get().getDownloadedUpdate()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restart() {
|
private void restart() {
|
||||||
AppUpdater.get().refreshUpdateCheckSilent();
|
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheckSilent();
|
||||||
UpdateAvailableAlert.showIfNeeded();
|
UpdateAvailableAlert.showIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download() {
|
|
||||||
AppUpdater.get().downloadUpdateAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
AppUpdater.get().checkForUpdateAsync();
|
ThreadHelper.runFailableAsync(() -> {
|
||||||
|
XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
|
||||||
|
XPipeDistributionType.get().getUpdateHandler().prepareUpdate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObservableValue<String> descriptionText() {
|
private ObservableValue<String> descriptionText() {
|
||||||
return PlatformThread.sync(Bindings.createStringBinding(
|
return PlatformThread.sync(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (AppUpdater.get().getDownloadedUpdate().getValue() != null) {
|
if (XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null) {
|
||||||
return AppI18n.get("updateRestart");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AppUpdater.get().getLastUpdateCheckResult().getValue() != null
|
if (XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult().getValue() != null
|
||||||
&& AppUpdater.get()
|
&& XPipeDistributionType.get().getUpdateHandler()
|
||||||
.getLastUpdateCheckResult()
|
.getLastUpdateCheckResult()
|
||||||
.getValue()
|
.getValue()
|
||||||
.isUpdate()) {
|
.isUpdate()) {
|
||||||
return AppI18n.get(
|
return AppI18n.get(
|
||||||
"updateAvailable",
|
"updateAvailable",
|
||||||
AppUpdater.get()
|
XPipeDistributionType.get().getUpdateHandler()
|
||||||
.getLastUpdateCheckResult()
|
.getLastUpdateCheckResult()
|
||||||
.getValue()
|
.getValue()
|
||||||
.getVersion());
|
.getVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AppUpdater.get().getLastUpdateCheckResult().getValue() != null) {
|
if (XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult().getValue() != null) {
|
||||||
return AppI18n.readableDuration(
|
return AppI18n.readableDuration(
|
||||||
new SimpleObjectProperty<>(AppUpdater.get()
|
new SimpleObjectProperty<>(XPipeDistributionType.get().getUpdateHandler()
|
||||||
.getLastUpdateCheckResult()
|
.getLastUpdateCheckResult()
|
||||||
.getValue()
|
.getValue()
|
||||||
.getCheckTime()),
|
.getCheckTime()),
|
||||||
|
@ -85,15 +72,15 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
AppUpdater.get().getLastUpdateCheckResult(),
|
XPipeDistributionType.get().getUpdateHandler().getLastUpdateCheckResult(),
|
||||||
AppUpdater.get().getDownloadedUpdate(),
|
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate(),
|
||||||
AppUpdater.get().getBusy()));
|
XPipeDistributionType.get().getUpdateHandler().getBusy()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Region createSimple() {
|
protected Region createSimple() {
|
||||||
var button = new Button();
|
var button = new Button();
|
||||||
button.disableProperty().bind(PlatformThread.sync(AppUpdater.get().getBusy()));
|
button.disableProperty().bind(PlatformThread.sync(XPipeDistributionType.get().getUpdateHandler().getBusy()));
|
||||||
button.textProperty()
|
button.textProperty()
|
||||||
.bind(Bindings.createStringBinding(
|
.bind(Bindings.createStringBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -101,30 +88,18 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
return AppI18n.get("updateReady");
|
return AppI18n.get("updateReady");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAvailable.getValue()) {
|
return AppI18n.get("checkForUpdates");
|
||||||
return XPipeDistributionType.get().supportsUpdate()
|
|
||||||
? AppI18n.get("downloadUpdate")
|
|
||||||
: AppI18n.get("checkOutUpdate");
|
|
||||||
} else {
|
|
||||||
return AppI18n.get("checkForUpdates");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
updateAvailable,
|
|
||||||
updateReady));
|
updateReady));
|
||||||
button.graphicProperty()
|
button.graphicProperty()
|
||||||
.bind(Bindings.createObjectBinding(
|
.bind(Bindings.createObjectBinding(
|
||||||
() -> {
|
() -> {
|
||||||
if (updateReady.getValue()) {
|
if (updateReady.getValue()) {
|
||||||
return new FontIcon("mdi2r-restart");
|
return new FontIcon("mdi2a-apple-airplay");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAvailable.getValue()) {
|
return new FontIcon("mdi2r-refresh");
|
||||||
return new FontIcon("mdi2d-download");
|
|
||||||
} else {
|
|
||||||
return new FontIcon("mdi2r-refresh");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
updateAvailable,
|
|
||||||
updateReady));
|
updateReady));
|
||||||
button.getStyleClass().add("button-comp");
|
button.getStyleClass().add("button-comp");
|
||||||
button.setOnAction(e -> {
|
button.setOnAction(e -> {
|
||||||
|
@ -133,14 +108,7 @@ public class UpdateCheckComp extends SimpleComp {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAvailable.getValue() && !XPipeDistributionType.get().supportsUpdate()) {
|
refresh();
|
||||||
Hyperlinks.open(
|
|
||||||
AppUpdater.get().getLastUpdateCheckResult().getValue().getReleaseUrl());
|
|
||||||
} else if (updateAvailable.getValue()) {
|
|
||||||
download();
|
|
||||||
} else {
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var checked = new Label();
|
var checked = new Label();
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
package io.xpipe.app.comp.base;
|
package io.xpipe.app.comp.base;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.CompStructure;
|
import io.xpipe.app.fxcomps.CompStructure;
|
||||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||||
import io.xpipe.app.fxcomps.augment.GrowAugment;
|
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.property.Property;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.css.PseudoClass;
|
import javafx.css.PseudoClass;
|
||||||
|
@ -40,6 +45,18 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||||
});
|
});
|
||||||
vbox.getChildren().add(b.createRegion());
|
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");
|
vbox.getStyleClass().add("sidebar-comp");
|
||||||
return new SimpleCompStructure<>(vbox);
|
return new SimpleCompStructure<>(vbox);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import io.xpipe.app.comp.AppLayoutComp;
|
||||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
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 io.xpipe.core.process.OsType;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -76,21 +76,17 @@ public class App extends Application {
|
||||||
() -> {
|
() -> {
|
||||||
var base = String.format(
|
var base = String.format(
|
||||||
"X-Pipe Desktop (%s)", AppProperties.get().getVersion());
|
"X-Pipe Desktop (%s)", AppProperties.get().getVersion());
|
||||||
var suffix = AppUpdater.get().getLastUpdateCheckResult().getValue() != null
|
var suffix = XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate().getValue() != null
|
||||||
&& AppUpdater.get()
|
|
||||||
.getLastUpdateCheckResult()
|
|
||||||
.getValue()
|
|
||||||
.isUpdate()
|
|
||||||
? String.format(
|
? String.format(
|
||||||
" (Update to %s available)",
|
" (Update to %s ready)",
|
||||||
AppUpdater.get()
|
XPipeDistributionType.get().getUpdateHandler()
|
||||||
.getLastUpdateCheckResult()
|
.getPreparedUpdate()
|
||||||
.getValue()
|
.getValue()
|
||||||
.getVersion())
|
.getVersion())
|
||||||
: "";
|
: "";
|
||||||
return base + suffix;
|
return base + suffix;
|
||||||
},
|
},
|
||||||
AppUpdater.get().getLastUpdateCheckResult());
|
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate());
|
||||||
|
|
||||||
var appWindow = new AppMainWindow(stage);
|
var appWindow = new AppMainWindow(stage);
|
||||||
appWindow.getStage().titleProperty().bind(PlatformThread.sync(titleBinding));
|
appWindow.getStage().titleProperty().bind(PlatformThread.sync(titleBinding));
|
||||||
|
|
|
@ -3,10 +3,12 @@ package io.xpipe.app.core.mode;
|
||||||
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
import io.xpipe.app.comp.storage.collection.SourceCollectionViewState;
|
||||||
import io.xpipe.app.comp.storage.store.StoreViewState;
|
import io.xpipe.app.comp.storage.store.StoreViewState;
|
||||||
import io.xpipe.app.core.*;
|
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.prefs.AppPrefs;
|
||||||
import io.xpipe.app.storage.DataStorage;
|
import io.xpipe.app.storage.DataStorage;
|
||||||
import io.xpipe.app.update.AppUpdater;
|
|
||||||
import io.xpipe.app.util.FileBridge;
|
import io.xpipe.app.util.FileBridge;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
|
||||||
|
@ -40,7 +42,6 @@ public class BaseMode extends OperationMode {
|
||||||
AppFileWatcher.init();
|
AppFileWatcher.init();
|
||||||
FileBridge.init();
|
FileBridge.init();
|
||||||
AppSocketServer.init();
|
AppSocketServer.init();
|
||||||
AppUpdater.init();
|
|
||||||
TrackEvent.info("mode", "Finished base components initialization");
|
TrackEvent.info("mode", "Finished base components initialization");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.app.launcher.LauncherCommand;
|
import io.xpipe.app.launcher.LauncherCommand;
|
||||||
import io.xpipe.app.util.ThreadHelper;
|
import io.xpipe.app.util.ThreadHelper;
|
||||||
import io.xpipe.core.util.XPipeDaemonMode;
|
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 org.apache.commons.lang3.function.FailableRunnable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.sentry.protocol.User;
|
||||||
import io.xpipe.app.core.AppCache;
|
import io.xpipe.app.core.AppCache;
|
||||||
import io.xpipe.app.core.AppProperties;
|
import io.xpipe.app.core.AppProperties;
|
||||||
import io.xpipe.app.prefs.AppPrefs;
|
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 org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
|
@ -3,8 +3,8 @@ package io.xpipe.app.issue;
|
||||||
import io.sentry.Sentry;
|
import io.sentry.Sentry;
|
||||||
import io.xpipe.app.core.*;
|
import io.xpipe.app.core.*;
|
||||||
import io.xpipe.app.core.mode.OperationMode;
|
import io.xpipe.app.core.mode.OperationMode;
|
||||||
import io.xpipe.app.update.AppUpdater;
|
|
||||||
import io.xpipe.app.util.Hyperlinks;
|
import io.xpipe.app.util.Hyperlinks;
|
||||||
|
import io.xpipe.app.update.XPipeDistributionType;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonBar;
|
import javafx.scene.control.ButtonBar;
|
||||||
|
@ -87,8 +87,7 @@ public class TerminalErrorHandler implements ErrorHandler {
|
||||||
|
|
||||||
private static void handleProbableUpdate() {
|
private static void handleProbableUpdate() {
|
||||||
try {
|
try {
|
||||||
AppUpdater.init();
|
var rel = XPipeDistributionType.get().getUpdateHandler().refreshUpdateCheck();
|
||||||
var rel = AppUpdater.get().refreshUpdateCheck();
|
|
||||||
if (rel != null && rel.isUpdate()) {
|
if (rel != null && rel.isUpdate()) {
|
||||||
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
alert.setAlertType(Alert.AlertType.INFORMATION);
|
alert.setAlertType(Alert.AlertType.INFORMATION);
|
||||||
|
|
|
@ -14,7 +14,6 @@ import io.xpipe.app.ext.PrefsHandler;
|
||||||
import io.xpipe.app.ext.PrefsProvider;
|
import io.xpipe.app.ext.PrefsProvider;
|
||||||
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.util.XPipeDistributionType;
|
|
||||||
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;
|
||||||
|
@ -146,13 +145,14 @@ public class AppPrefs {
|
||||||
|
|
||||||
// Automatically update
|
// Automatically update
|
||||||
// ====================
|
// ====================
|
||||||
private final BooleanProperty automaticallyUpdate =
|
private final BooleanProperty automaticallyCheckForUpdates =
|
||||||
typed(new SimpleBooleanProperty(XPipeDistributionType.get().supportsUpdate()), Boolean.class);
|
typed(new SimpleBooleanProperty(true), Boolean.class);
|
||||||
private final BooleanField automaticallyUpdateField =
|
private final BooleanField automaticallyCheckForUpdatesField =
|
||||||
BooleanField.ofBooleanType(automaticallyUpdate).render(() -> new CustomToggleControl());
|
BooleanField.ofBooleanType(automaticallyCheckForUpdates).render(() -> new CustomToggleControl());
|
||||||
private final BooleanProperty updateToPrereleases = typed(new SimpleBooleanProperty(false), Boolean.class);
|
|
||||||
private final BooleanField updateToPrereleasesField =
|
private final BooleanProperty checkForPrereleases = typed(new SimpleBooleanProperty(false), Boolean.class);
|
||||||
BooleanField.ofBooleanType(updateToPrereleases).render(() -> new CustomToggleControl());
|
private final BooleanField checkForPrereleasesField =
|
||||||
|
BooleanField.ofBooleanType(checkForPrereleases).render(() -> new CustomToggleControl());
|
||||||
|
|
||||||
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
|
private final BooleanProperty confirmDeletions = typed(new SimpleBooleanProperty(true), Boolean.class);
|
||||||
|
|
||||||
|
@ -236,11 +236,11 @@ public class AppPrefs {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty automaticallyUpdate() {
|
public ReadOnlyBooleanProperty automaticallyUpdate() {
|
||||||
return automaticallyUpdate;
|
return automaticallyCheckForUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty updateToPrereleases() {
|
public ReadOnlyBooleanProperty updateToPrereleases() {
|
||||||
return updateToPrereleases;
|
return checkForPrereleases;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty confirmDeletions() {
|
public ReadOnlyBooleanProperty confirmDeletions() {
|
||||||
|
@ -431,12 +431,8 @@ public class AppPrefs {
|
||||||
Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)),
|
Setting.of("closeBehaviour", closeBehaviourControl, closeBehaviour)),
|
||||||
Group.of(
|
Group.of(
|
||||||
"updates",
|
"updates",
|
||||||
Setting.of("automaticallyUpdate", automaticallyUpdateField, automaticallyUpdate)
|
Setting.of("automaticallyUpdate", automaticallyCheckForUpdatesField, automaticallyCheckForUpdates),
|
||||||
.applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty(
|
Setting.of("updateToPrereleases", checkForPrereleasesField, checkForPrereleases)),
|
||||||
XPipeDistributionType.get().supportsUpdate()))),
|
|
||||||
Setting.of("updateToPrereleases", updateToPrereleasesField, updateToPrereleases)
|
|
||||||
.applyVisibility(VisibilityProperty.of(new SimpleBooleanProperty(
|
|
||||||
XPipeDistributionType.get().supportsUpdate())))),
|
|
||||||
Group.of(
|
Group.of(
|
||||||
"advanced",
|
"advanced",
|
||||||
Setting.of("storageDirectory", storageDirectoryControl, internalStorageDirectory),
|
Setting.of("storageDirectory", storageDirectoryControl, internalStorageDirectory),
|
||||||
|
|
|
@ -8,6 +8,7 @@ import io.xpipe.core.impl.FileNames;
|
||||||
import io.xpipe.core.impl.LocalStore;
|
import io.xpipe.core.impl.LocalStore;
|
||||||
import io.xpipe.core.process.OsType;
|
import io.xpipe.core.process.OsType;
|
||||||
import io.xpipe.core.process.ShellControl;
|
import io.xpipe.core.process.ShellControl;
|
||||||
|
import io.xpipe.core.process.ShellDialects;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -64,7 +65,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
new SimpleType("gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
|
new SimpleType("gnomeTerminal", "gnome-terminal", "Gnome Terminal") {
|
||||||
|
|
||||||
@Override
|
@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()) {
|
try (ShellControl pc = LocalStore.getShell()) {
|
||||||
ApplicationHelper.checkSupport(pc, executable, getDisplayName());
|
ApplicationHelper.checkSupport(pc, executable, getDisplayName());
|
||||||
|
|
||||||
|
@ -144,7 +145,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
.orElse(null);
|
.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 {
|
static class MacOsTerminalType extends ExternalApplicationType.MacApplication implements ExternalTerminalType {
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()) {
|
try (ShellControl pc = LocalStore.getShell()) {
|
||||||
var suffix = file.equals(pc.getShellDialect().getOpenCommand())
|
var suffix = file.equals(pc.getShellDialect().getOpenCommand())
|
||||||
? "\"\""
|
? "\"\""
|
||||||
|
@ -171,7 +172,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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();
|
var custom = AppPrefs.get().customTerminalCommand().getValue();
|
||||||
if (custom == null || custom.isBlank()) {
|
if (custom == null || custom.isBlank()) {
|
||||||
throw new IllegalStateException("No custom terminal command specified");
|
throw new IllegalStateException("No custom terminal command specified");
|
||||||
|
@ -207,7 +208,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()) {
|
try (ShellControl pc = LocalStore.getShell()) {
|
||||||
var cmd = String.format(
|
var cmd = String.format(
|
||||||
"""
|
"""
|
||||||
|
@ -241,7 +242,7 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launch(String name, String file) throws Exception {
|
public void launch(String name, String file, boolean elevated) throws Exception {
|
||||||
if (!MacOsPermissions.waitForAccessibilityPermissions()) {
|
if (!MacOsPermissions.waitForAccessibilityPermissions()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -279,7 +280,18 @@ public interface ExternalTerminalType extends PrefsChoiceValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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()) {
|
try (ShellControl pc = LocalStore.getShell()) {
|
||||||
ApplicationHelper.checkSupport(pc, executable, displayName);
|
ApplicationHelper.checkSupport(pc, executable, displayName);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.storage;
|
||||||
|
|
||||||
import io.xpipe.app.issue.ErrorEvent;
|
import io.xpipe.app.issue.ErrorEvent;
|
||||||
import io.xpipe.app.issue.TrackEvent;
|
import io.xpipe.app.issue.TrackEvent;
|
||||||
import io.xpipe.core.util.XPipeSession;
|
import io.xpipe.app.util.XPipeSession;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.xpipe.beacon.BeaconDaemonController;
|
||||||
import io.xpipe.core.store.DataStore;
|
import io.xpipe.core.store.DataStore;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
import io.xpipe.core.util.XPipeDaemonMode;
|
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.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ package io.xpipe.app.test;
|
||||||
|
|
||||||
import io.xpipe.app.ext.XPipeServiceProviders;
|
import io.xpipe.app.ext.XPipeServiceProviders;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
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 org.junit.jupiter.api.BeforeAll;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
49
app/src/main/java/io/xpipe/app/update/ChocoUpdater.java
Normal file
49
app/src/main/java/io/xpipe/app/update/ChocoUpdater.java
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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=" + getLastUpdateCheckResult().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(),
|
||||||
|
latest,
|
||||||
|
"https://community.chocolatey.org/packages/xpipe/" + latest,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now(),
|
||||||
|
isUpdate);
|
||||||
|
lastUpdateCheckResult.setValue(rel);
|
||||||
|
return lastUpdateCheckResult.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
app/src/main/java/io/xpipe/app/update/GitHubUpdater.java
Normal file
82
app/src/main/java/io/xpipe/app/update/GitHubUpdater.java
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
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(),
|
||||||
|
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(),
|
||||||
|
rel.get().getTagName(),
|
||||||
|
rel.get().getHtmlUrl().toString(),
|
||||||
|
ghAsset.get().getBrowserDownloadUrl(),
|
||||||
|
assetType,
|
||||||
|
Instant.now(),
|
||||||
|
isUpdate));
|
||||||
|
return lastUpdateCheckResult.getValue();
|
||||||
|
}
|
||||||
|
}
|
54
app/src/main/java/io/xpipe/app/update/PortableUpdater.java
Normal file
54
app/src/main/java/io/xpipe/app/update/PortableUpdater.java
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
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(),
|
||||||
|
rel.get().getTagName(),
|
||||||
|
rel.get().getHtmlUrl().toString(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
Instant.now(),
|
||||||
|
isUpdate));
|
||||||
|
return lastUpdateCheckResult.getValue();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,42 +3,51 @@ package io.xpipe.app.update;
|
||||||
import io.xpipe.app.comp.base.MarkdownComp;
|
import io.xpipe.app.comp.base.MarkdownComp;
|
||||||
import io.xpipe.app.core.AppI18n;
|
import io.xpipe.app.core.AppI18n;
|
||||||
import io.xpipe.app.core.AppWindowHelper;
|
import io.xpipe.app.core.AppWindowHelper;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonBar;
|
import javafx.scene.control.ButtonBar;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
public class UpdateAvailableAlert {
|
public class UpdateAvailableAlert {
|
||||||
|
|
||||||
public static void showIfNeeded() {
|
public static void showIfNeeded() {
|
||||||
if (AppUpdater.get().getDownloadedUpdate().getValue() == null) {
|
UpdateHandler uh = XPipeDistributionType.get().getUpdateHandler();
|
||||||
|
if (uh.getPreparedUpdate().getValue() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var u = AppUpdater.get().getDownloadedUpdate().getValue();
|
var u = uh.getPreparedUpdate().getValue();
|
||||||
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
var update = AppWindowHelper.showBlockingAlert(alert -> {
|
||||||
alert.setTitle(AppI18n.get("updateReadyAlertTitle"));
|
alert.setTitle(AppI18n.get("updateReadyAlertTitle"));
|
||||||
alert.setAlertType(Alert.AlertType.NONE);
|
alert.setAlertType(Alert.AlertType.NONE);
|
||||||
|
|
||||||
if (u.getBody() != null && !u.getBody().isBlank()) {
|
var markdown = new MarkdownComp(u.getBody() != null ? u.getBody() : "", s -> {
|
||||||
var markdown = new MarkdownComp(u.getBody(), s -> {
|
var header = "<h1>" + AppI18n.get("whatsNew", u.getVersion()) + "</h1>";
|
||||||
var header = "<h1>" + AppI18n.get("whatsNew", u.getVersion()) + "</h1>";
|
return header + s;
|
||||||
return header + s;
|
})
|
||||||
})
|
.createRegion();
|
||||||
.createRegion();
|
alert.getButtonTypes().clear();
|
||||||
alert.getDialogPane().setContent(markdown);
|
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 {
|
} else {
|
||||||
alert.getDialogPane()
|
alert.getDialogPane().setContent(markdown);
|
||||||
.setContent(AppWindowHelper.alertContentText(AppI18n.get("updateReadyAlertContent")));
|
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));
|
alert.getButtonTypes().add(new ButtonType(AppI18n.get("ignore"), ButtonBar.ButtonData.NO));
|
||||||
})
|
})
|
||||||
.map(buttonType -> buttonType.getButtonData().isDefaultButton())
|
.map(buttonType -> buttonType.getButtonData().isDefaultButton())
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
if (update) {
|
if (update) {
|
||||||
AppUpdater.get().executeUpdateAndClose();
|
uh.executeUpdateAndClose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,8 @@ import javafx.stage.Modality;
|
||||||
public class UpdateChangelogAlert {
|
public class UpdateChangelogAlert {
|
||||||
|
|
||||||
public static void showIfNeeded() {
|
public static void showIfNeeded() {
|
||||||
var update = AppUpdater.get().getPerformedUpdate();
|
var update = XPipeDistributionType.get().getUpdateHandler().getPerformedUpdate();
|
||||||
|
if (update != null && !XPipeDistributionType.get().getUpdateHandler().isUpdateSucceeded()) {
|
||||||
if (update != null && !AppUpdater.get().isUpdateSucceeded()) {
|
|
||||||
ErrorEvent.fromMessage("Update did not succeed").handle();
|
ErrorEvent.fromMessage("Update did not succeed").handle();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
259
app/src/main/java/io/xpipe/app/update/UpdateHandler.java
Normal file
259
app/src/main/java/io/xpipe/app/update/UpdateHandler.java
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
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("lastUpdateCheckResult");
|
||||||
|
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())) {
|
||||||
|
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(),
|
||||||
|
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 version;
|
||||||
|
String releaseUrl;
|
||||||
|
String downloadUrl;
|
||||||
|
AppInstaller.InstallerAssetType assetType;
|
||||||
|
Instant checkTime;
|
||||||
|
boolean isUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
public static class PreparedUpdate {
|
||||||
|
String sourceVersion;
|
||||||
|
String version;
|
||||||
|
String releaseUrl;
|
||||||
|
Path file;
|
||||||
|
String body;
|
||||||
|
AppInstaller.InstallerAssetType assetType;
|
||||||
|
}
|
||||||
|
}
|
112
app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java
Normal file
112
app/src/main/java/io/xpipe/app/update/XPipeDistributionType.java
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
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 io.xpipe.core.util.ModuleHelper;
|
||||||
|
import io.xpipe.core.util.XPipeInstallation;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public enum XPipeDistributionType {
|
||||||
|
DEVELOPMENT("development", () -> new GitHubUpdater(false)) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "development";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PORTABLE("portable", () -> new PortableUpdater()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "portable";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
INSTALLATION("install", () -> new GitHubUpdater(true)) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "install";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CHOCO("choco", () -> new ChocoUpdater()) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "choco";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (!ModuleHelper.isImage()) {
|
||||||
|
return (type = DEVELOPMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (!XPipeInstallation.isInstallationDistribution()) {
|
||||||
|
return (type = PORTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getName();
|
||||||
|
}
|
|
@ -21,6 +21,6 @@ public class TerminalHelper {
|
||||||
throw new IllegalStateException(AppI18n.get("noTerminalSet"));
|
throw new IllegalStateException(AppI18n.get("noTerminalSet"));
|
||||||
}
|
}
|
||||||
|
|
||||||
type.launch(title, command);
|
type.launch(title, command, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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.process.OsType;
|
||||||
|
import io.xpipe.core.util.UuidHelper;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -13,6 +16,8 @@ public class XPipeSession {
|
||||||
|
|
||||||
boolean isNewSystemSession;
|
boolean isNewSystemSession;
|
||||||
|
|
||||||
|
boolean isNewBuildSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier that resets on every X-Pipe restart.
|
* Unique identifier that resets on every X-Pipe restart.
|
||||||
*/
|
*/
|
||||||
|
@ -61,7 +66,11 @@ public class XPipeSession {
|
||||||
} catch (Exception ignored) {
|
} 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() {
|
public static XPipeSession get() {
|
|
@ -16,7 +16,7 @@ saveWindowLocation=Save window location
|
||||||
saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts.
|
saveWindowLocationDescription=Controls whether the window coordinates should be saved and restored on restarts.
|
||||||
startupShutdown=Startup / Shutdown
|
startupShutdown=Startup / Shutdown
|
||||||
system=System
|
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.
|
updateToPrereleasesDescription=When enabled, the update check will also look for available prereleases in addition to full releases.
|
||||||
storage=Storage
|
storage=Storage
|
||||||
runOnStartup=Run on startup
|
runOnStartup=Run on startup
|
||||||
|
@ -45,8 +45,8 @@ cancel=Cancel
|
||||||
notAnAbsolutePath=Not an absolute path
|
notAnAbsolutePath=Not an absolute path
|
||||||
notADirectory=Not a directory
|
notADirectory=Not a directory
|
||||||
notAnEmptyDirectory=Not an empty directory
|
notAnEmptyDirectory=Not an empty directory
|
||||||
automaticallyUpdate=Automatically update
|
automaticallyUpdate=Check for updates
|
||||||
automaticallyUpdateDescription=When enabled, new releases are automatically downloaded in the background while X-Pipe is running and installed on the next launch.
|
automaticallyUpdateDescription=When enabled, new releases are automatically fetched in the background while X-Pipe is running.
|
||||||
sendAnonymousErrorReports=Send anonymous error reports
|
sendAnonymousErrorReports=Send anonymous error reports
|
||||||
sendUsageStatistics=Send anonymous usage statistics
|
sendUsageStatistics=Send anonymous usage statistics
|
||||||
storageDirectory=Storage directory
|
storageDirectory=Storage directory
|
||||||
|
|
Loading…
Reference in a new issue