true if an update is available, false otherwise. Always true for SNAPSHOT versions.
+ */
+ static boolean isUpdateAvailable(String updateVersion, String installedVersion) {
+ if (installedVersion.contains("SNAPSHOT")) {
+ return true; // SNAPSHOT versions are always considered to be outdated.
+ } else {
+ return SemVerComparator.INSTANCE.compare(updateVersion, installedVersion) > 0;
+ }
+ }
+
+ /**
+ * Checks whether an update is available.
+ * @param currentVersion The full version string of the currently installed application, e.g. "1.2.3-beta4".
+ * @param httpClient An HTTP client that can be used to check for updates.
+ * @return An {@link UpdateInfo} if an update is available, or null otherwise.
+ * @throws UpdateFailedException If the availability of an update could not be determined
+ */
+ @Blocking
+ @Nullable
+ T checkForUpdate(String currentVersion, HttpClient httpClient) throws UpdateFailedException;
+
+ /**
+ * Returns the first step to prepare the update. This can be anything like downloading the update, checking signatures, etc.
+ * @param updateInfo The {@link UpdateInfo} representing the update to be prepared.
+ * @return a new {@link UpdateStep} that can be used to monitor the progress of the update preparation. The task will complete when the preparation is done.
+ * @throws UpdateFailedException If no update process can be started, e.g. due to network or I/O issues.
+ */
+ @NotNull
+ UpdateStep firstStep(T updateInfo) throws UpdateFailedException;
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/integrations/update/UpdateStep.java b/src/main/java/org/cryptomator/integrations/update/UpdateStep.java
new file mode 100644
index 0000000..b2cd9b5
--- /dev/null
+++ b/src/main/java/org/cryptomator/integrations/update/UpdateStep.java
@@ -0,0 +1,111 @@
+package org.cryptomator.integrations.update;
+
+import org.cryptomator.integrations.Localization;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NonBlocking;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Range;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+@ApiStatus.Experimental
+public interface UpdateStep {
+
+ /**
+ * A magic constant indicating that the application shall terminate.
+ *
+ * This step can be returned as the last step of the update process, usually immediately after a restart has been scheduled.
+ */
+ UpdateStep EXIT = new NoopUpdateStep(Localization.get().getString("org.cryptomator.api.update.updateStep.EXIT"));
+
+ /**
+ * A magic constant indicating that the update process shall be retried.
+ */
+ UpdateStep RETRY = new NoopUpdateStep(Localization.get().getString("org.cryptomator.api.update.updateStep.RETRY"));
+
+
+ static UpdateStep of(String name, Callable
+ * If the step is already complete, this method returns immediately.
+ *
+ * @throws InterruptedException if the current thread is interrupted while waiting.
+ */
+ void await() throws InterruptedException;
+
+ /**
+ * Blocks the current thread until this update step completed or an error occurred, or until the specified timeout expires.
+ * If this step failed, an exception will be rethrown as soon as attempting to invoke {@link #nextStep()}.
+ *
+ * If the step is already complete, this method returns immediately.
+ *
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the {@code timeout} argument
+ * @return true if the update is prepared
+ */
+ boolean await(long timeout, TimeUnit unit) throws InterruptedException;
+
+ default boolean isDone() {
+ try {
+ return await(0, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ /**
+ * After running this step to completion, this method returns the next step of the update process.
+ *
+ * @return the next {@link UpdateStep step} of the update process or null if this was the final step.
+ * @throws IllegalStateException if this step didn't complete yet or other preconditions aren't met.
+ * @throws IOException indicating an error before reaching the next step, e.g. during execution of this step.
+ * @implSpec The returned {@link UpdateStep} must either be stateless or a new instance must be returned on each call.
+ */
+ @Nullable
+ UpdateStep nextStep() throws IllegalStateException, IOException;
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/integrations/update/UpdateStepAdapter.java b/src/main/java/org/cryptomator/integrations/update/UpdateStepAdapter.java
new file mode 100644
index 0000000..dc0cd0d
--- /dev/null
+++ b/src/main/java/org/cryptomator/integrations/update/UpdateStepAdapter.java
@@ -0,0 +1,65 @@
+package org.cryptomator.integrations.update;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+public abstract class UpdateStepAdapter implements Callable