Various bug fixes for updater, exit behaviour, error handling

This commit is contained in:
crschnick 2023-10-11 09:54:02 +00:00
parent b507ed8a11
commit a37821e22c
21 changed files with 159 additions and 86 deletions

View file

@ -74,14 +74,8 @@ public class StoreSection {
private static ObservableList<StoreSection> sorted(
ObservableList<StoreSection> list, ObservableValue<StoreCategoryWrapper> category) {
var c = Comparator.<StoreSection>comparingInt(
value -> value.getWrapper().getEntry().getValidity().isUsable() ? 1 : -1);
category.getValue().getSortMode().addListener((observable, oldValue, newValue) -> {
int a = 0;
});
value -> value.getWrapper().getEntry().getValidity().isUsable() ? -1 : 1);
var mapped = BindingsHelper.mappedBinding(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode());
mapped.addListener((observable, oldValue, newValue) -> {
int a = 0;
});
return BindingsHelper.orderedContentBinding(
list,
(o1, o2) -> {

View file

@ -117,13 +117,24 @@ public class StoreViewState {
}
public static void init() {
if (INSTANCE != null) {
return;
}
new StoreViewState();
}
public static void reset() {
AppCache.update(
"selectedCategory",
INSTANCE.activeCategory.getValue().getCategory().getUuid());
if (INSTANCE == null) {
return;
}
var active = INSTANCE.activeCategory.getValue().getCategory();
if (active == null) {
return;
}
AppCache.update("selectedCategory", active.getUuid());
INSTANCE = null;
}

View file

@ -3,10 +3,10 @@ package io.xpipe.app.core.mode;
import io.xpipe.app.core.App;
import io.xpipe.app.core.AppGreetings;
import io.xpipe.app.core.AppMainWindow;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.*;
import io.xpipe.app.update.CommercializationAlert;
import io.xpipe.app.update.UpdateChangelogAlert;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.UnlockAlert;
import javafx.application.Platform;
@ -21,7 +21,7 @@ public class GuiMode extends PlatformMode {
@Override
public void onSwitchTo() throws Throwable {
super.platformSetup();
super.onSwitchTo();
UnlockAlert.showIfNeeded();
UpdateChangelogAlert.showIfNeeded();
@ -53,11 +53,11 @@ public class GuiMode extends PlatformMode {
@Override
public void onSwitchFrom() {
if (PlatformState.getCurrent() == PlatformState.RUNNING) {
super.onSwitchFrom();
PlatformThread.runLaterIfNeededBlocking(() -> {
TrackEvent.info("mode", "Closing window");
App.getApp().close();
waitForPlatform();
}
});
}
@Override

View file

@ -11,6 +11,7 @@ import io.xpipe.core.util.FailableRunnable;
import io.xpipe.core.util.XPipeDaemonMode;
import io.xpipe.core.util.XPipeInstallation;
import io.xpipe.core.util.XPipeSystemId;
import javafx.application.Platform;
import java.util.ArrayList;
import java.util.List;
@ -72,6 +73,7 @@ public abstract class OperationMode {
try {
// Only for handling SIGTERM
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
TrackEvent.info("Received SIGTERM externally");
OperationMode.shutdown(true, false);
}));
@ -178,8 +180,7 @@ public abstract class OperationMode {
}
public static void executeAfterShutdown(FailableRunnable<Exception> r) {
// Creates separate non daemon thread to force execution after shutdown even if current thread is a daemon
var t = new Thread(() -> {
Runnable exec = () -> {
if (inShutdown) {
return;
}
@ -198,12 +199,19 @@ public abstract class OperationMode {
}
OperationMode.halt(0);
});
t.setDaemon(false);
t.start();
try {
t.join();
} catch (InterruptedException ignored) {
};
if (Platform.isFxApplicationThread() || !Thread.currentThread().isDaemon()) {
exec.run();
} else {
// Creates separate non daemon thread to force execution after shutdown even if current thread is a daemon
var t = new Thread(exec);
t.setDaemon(false);
t.start();
try {
t.join();
} catch (InterruptedException ignored) {
}
}
}

View file

@ -8,10 +8,6 @@ import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Application;
import javafx.application.Platform;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public abstract class PlatformMode extends OperationMode {
@ -21,7 +17,8 @@ public abstract class PlatformMode extends OperationMode {
return PlatformState.getCurrent() == PlatformState.RUNNING;
}
protected void platformSetup() throws Throwable {
@Override
public void onSwitchTo() throws Throwable {
if (App.getApp() != null) {
return;
}
@ -60,25 +57,9 @@ public abstract class PlatformMode extends OperationMode {
StoreViewState.init();
}
protected void waitForPlatform() {
// The platform thread waits for the shutdown hook to finish in case SIGTERM is sent.
// Therefore, we do not wait for the platform when being in a shutdown hook.
if (PlatformState.getCurrent() == PlatformState.RUNNING
&& !Platform.isFxApplicationThread()
&& !OperationMode.isInShutdownHook()) {
TrackEvent.info("mode", "Waiting for platform thread ...");
CountDownLatch latch = new CountDownLatch(1);
Platform.runLater(latch::countDown);
try {
if (!latch.await(5, TimeUnit.SECONDS)) {
TrackEvent.info("mode", "Platform wait timed out");
}
} catch (InterruptedException ignored) {
}
TrackEvent.info("mode", "Synced with platform thread");
} else {
TrackEvent.info("mode", "Not waiting for platform thread");
}
@Override
public void onSwitchFrom() {
StoreViewState.reset();
}
@Override

View file

@ -2,6 +2,7 @@ package io.xpipe.app.core.mode;
import com.dustinredmond.fxtrayicon.FXTrayIcon;
import io.xpipe.app.core.AppTray;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.*;
public class TrayMode extends PlatformMode {
@ -18,24 +19,24 @@ public class TrayMode extends PlatformMode {
@Override
public void onSwitchTo() throws Throwable {
super.platformSetup();
super.onSwitchTo();
PlatformThread.runLaterIfNeededBlocking(() -> {
if (AppTray.get() == null) {
TrackEvent.info("mode", "Initializing tray");
AppTray.init();
}
if (AppTray.get() == null) {
TrackEvent.info("mode", "Initializing tray");
AppTray.init();
}
AppTray.get().show();
waitForPlatform();
TrackEvent.info("mode", "Finished tray initialization");
AppTray.get().show();
TrackEvent.info("mode", "Finished tray initialization");
});
}
@Override
public void onSwitchFrom() {
super.onSwitchFrom();
if (AppTray.get() != null) {
TrackEvent.info("mode", "Closing tray");
AppTray.get().hide();
waitForPlatform();
PlatformThread.runLaterIfNeededBlocking(() -> AppTray.get().hide());
}
}

View file

@ -1,6 +1,8 @@
package io.xpipe.app.fxcomps.util;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
@ -274,7 +276,24 @@ public class PlatformThread {
return obs;
}
private static boolean canRunPlatform() {
if (PlatformState.getCurrent() != PlatformState.RUNNING) {
return false;
}
// Once the shutdown hooks are run, the toolkit is shutdown, causing it to no longer perform runLater operations
if (OperationMode.isInShutdownHook()) {
return false;
}
return true;
}
public static void runLaterIfNeeded(Runnable r) {
if (!canRunPlatform()) {
return;
}
Runnable catcher = () -> {
try {
r.run();
@ -291,6 +310,10 @@ public class PlatformThread {
}
public static void runLaterIfNeededBlocking(Runnable r) {
if (!canRunPlatform()) {
return;
}
Runnable catcher = () -> {
try {
r.run();

View file

@ -26,6 +26,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableDoubleValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
@ -166,6 +167,17 @@ public class AppPrefs {
startupBehaviourList, startupBehaviour)
.render(() -> new TranslatableComboBoxControl<>());
// Git storage
// ===========
public final BooleanProperty enableGitStorage = typed(new SimpleBooleanProperty(false), Boolean.class);
public ObservableBooleanValue enableGitStorage() {
return enableGitStorage;
}
final StringProperty storageGitRemote = typed(new SimpleStringProperty(""), String.class);
public ObservableStringValue storageGitRemote() {
return storageGitRemote;
}
// Close behaviour
// ===============
private final ObjectProperty<CloseBehaviour> closeBehaviour =

View file

@ -3,6 +3,7 @@ package io.xpipe.app.prefs;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppWindowHelper;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.ext.PrefsChoiceValue;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
@ -14,6 +15,10 @@ import javafx.scene.layout.VBox;
public class CloseBehaviourAlert {
public static boolean showIfNeeded() {
if (OperationMode.isInShutdown()) {
return true;
}
boolean set = AppCache.get("closeBehaviourSet", Boolean.class, () -> false);
if (set) {
return true;

View file

@ -11,6 +11,7 @@ import com.dlsc.preferencesfx.util.PreferencesFxUtils;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
@ -71,14 +72,15 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
// add to GridPane
Element element = elements.get(i);
if (element instanceof Field f) {
var label = f.getLabel();
var descriptionKey = label != null ? label + "Description" : null;
SimpleControl c = (SimpleControl) ((Field) element).getRenderer();
c.setField((Field) element);
SimpleControl c = (SimpleControl) f.getRenderer();
c.setField(f);
AppFont.normal(c.getFieldLabel());
c.getFieldLabel().setPrefHeight(AppFont.getPixelSize(1));
c.getFieldLabel().setMaxHeight(AppFont.getPixelSize(1));
c.getFieldLabel().textProperty().unbind();
c.getFieldLabel().textProperty().bind(Bindings.createStringBinding(() -> {
return f.labelProperty().get() + (f.isEditable() ? "" : " (Pro)");
}, f.labelProperty()));
grid.add(c.getFieldLabel(), 0, i + rowAmount);
var canFocus = BindingsHelper.persist(
@ -101,6 +103,8 @@ public class CustomFormRenderer extends PreferencesFxFormRenderer {
descriptionLabel
.visibleProperty()
.bind(c.getFieldLabel().visibleProperty());
var descriptionKey = f.getLabel() != null ? f.getLabel() + "Description" : null;
if (AppI18n.getInstance().containsKey(descriptionKey)) {
rowAmount++;
descriptionLabel.textProperty().bind(AppI18n.observable(descriptionKey));

View file

@ -1,12 +1,16 @@
package io.xpipe.app.prefs;
import com.dlsc.formsfx.model.structure.BooleanField;
import com.dlsc.formsfx.model.structure.StringField;
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleControl;
import com.dlsc.preferencesfx.formsfx.view.controls.SimpleTextControl;
import com.dlsc.preferencesfx.model.Category;
import com.dlsc.preferencesfx.model.Group;
import com.dlsc.preferencesfx.model.Setting;
import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LicenseType;
import io.xpipe.app.util.LockChangeAlert;
import io.xpipe.core.util.XPipeInstallation;
import javafx.beans.binding.Bindings;
@ -53,13 +57,39 @@ public class VaultCategory extends AppPrefsCategory {
@SneakyThrows
public Category create() {
var pro = LicenseType.isAtLeast(
LicenseProvider.get().getLicenseType(), LicenseType.PROFESSIONAL);
BooleanField enable = BooleanField.ofBooleanType(prefs.enableGitStorage)
.editable(pro)
.render(() -> {
var c = new CustomToggleControl();
return c;
});
StringField remote = StringField.ofStringType(prefs.storageGitRemote)
.editable(pro)
.render(() -> {
var c = new SimpleTextControl();
c.setPrefWidth(1000);
return c;
});
return Category.of(
"vault",
group(
"sharing",
Setting.of(
"enableGitStorage",
enable,
prefs.enableGitStorage),
Setting.of(
"storageGitRemote",
remote,
prefs.storageGitRemote)),
group(
"storage",
STORAGE_DIR_FIXED
? null
: Setting.of("storageDirectory", prefs.storageDirectoryControl, prefs.storageDirectory)),
: Setting.of(
"storageDirectory", prefs.storageDirectoryControl, prefs.storageDirectory)),
Group.of("security", Setting.of("workspaceLock", lockCryptControl, prefs.getLockCrypt())));
}
}

View file

@ -3,6 +3,7 @@ package io.xpipe.app.update;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import io.xpipe.core.process.OsType;
@ -58,6 +59,7 @@ public enum XPipeDistributionType {
type = det;
AppCache.update("dist", type.getId());
TrackEvent.withInfo("Determined distribution type").tag("type",type.getId()).handle();
return type;
}

View file

@ -1,10 +1,9 @@
package io.xpipe.app.util;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.AccessLevel;
import lombok.experimental.FieldDefaults;
@EqualsAndHashCode(callSuper = true)
@Value
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class LicenseRequiredException extends RuntimeException {
String featureName;

View file

@ -25,6 +25,8 @@ public interface ShellControl extends ProcessControl {
ShellControl getMachineRootSession();
ShellControl withoutLicenseCheck();
String getOsName();
UUID getSystemId();

View file

@ -1,6 +1,7 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.util.DataStateProvider;
/**
* A data store represents some form of a location where data is stored, e.g. a file or a database.
@ -10,6 +11,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface DataStore {
default boolean isInStorage() {
return DataStateProvider.get().isInStorage(this);
}
default boolean isComplete() {
try {
checkComplete();

View file

@ -6,10 +6,6 @@ import java.util.function.Supplier;
public interface InternalCacheDataStore extends DataStore {
default boolean isInStorage() {
return DataStateProvider.get().isInStorage(this);
}
default <T> T getCache(String key, Class<T> c, T def) {
return DataStateProvider.get().getCache(this, key, c, () -> def);
}

View file

@ -51,7 +51,12 @@ public interface ShellStore extends DataStore, InternalCacheDataStore, Launchabl
@Override
default void validate() throws Exception {
try (ShellControl pc = control().start()) {}
var c = control();
if (!isInStorage()) {
c.withoutLicenseCheck();
}
try (ShellControl pc = c.start()) {}
}
default String queryMachineName() throws Exception {

8
dist/build.gradle vendored
View file

@ -14,14 +14,6 @@ repositories {
task dist(type: DefaultTask) {}
clean {
setDelete(Set.of())
doLast {
fileTree(dir: project.buildDir).exclude("jreleaser/**").visit { FileVisitDetails details ->
delete details.file
}
}
}
distTar {
enabled = false;

View file

@ -18,8 +18,8 @@ You will definitely notice this change when you connect to a system.
### Colors
For organization purposes when many connections are opened in the file base and terminals at the same time, you can now assign colors to connections.
These colors will be shown to identify tabs both within and outside XPipe for example in terminals.
You can now assign colors to connections for organizational purposes to help in situations when many connections are opened in the file browser and terminals at the same time.
These colors will be shown to identify tabs everywhere within XPipe and also outside of XPipe, for example in terminals.
### Other changes
@ -29,4 +29,6 @@ These colors will be shown to identify tabs both within and outside XPipe for ex
- Save configuration data more frequently to avoid any data loss
- Fix shutdown error caused by clipboard being inaccessible
- Fix some environment scripts not being sourced correctly
- Fix autoupdater not working properly
- Fix application not exiting properly on SIGTERM
- Many other small miscellaneous fixes and improvements

View file

@ -2,3 +2,4 @@
set CDS_JVM_OPTS=JVM-ARGS
chcp 65001 > NUL
CALL "%~dp0\..\runtime\bin\xpiped.bat" %*
pause

View file

@ -7,9 +7,9 @@ dependencies {
testImplementation project(':core')
testImplementation project(':app')
testImplementation "org.openjfx:javafx-base:20.0.1:win"
testImplementation "org.openjfx:javafx-controls:20.0.1:win"
testImplementation "org.openjfx:javafx-graphics:20.0.1:win"
testImplementation "org.openjfx:javafx-base:21:win"
testImplementation "org.openjfx:javafx-controls:21:win"
testImplementation "org.openjfx:javafx-graphics:21:win"
}
def attachDebugger = System.getProperty('idea.debugger.dispatch.addr') != null