Skip to content

Commit 2aa7b15

Browse files
hide internals behind new API facade
1 parent 4976dde commit 2aa7b15

File tree

2 files changed

+138
-76
lines changed

2 files changed

+138
-76
lines changed

src/main/java/module-info.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import org.cryptomator.integrations.quickaccess.QuickAccessService;
44
import org.cryptomator.integrations.revealpath.RevealPathService;
55
import org.cryptomator.integrations.tray.TrayMenuController;
6-
import org.cryptomator.integrations.update.UpdateService;
6+
import org.cryptomator.integrations.update.UpdateMechanism;
77
import org.cryptomator.linux.autostart.FreedesktopAutoStartService;
88
import org.cryptomator.linux.keychain.GnomeKeyringKeychainAccess;
99
import org.cryptomator.linux.keychain.KDEWalletKeychainAccess;
@@ -28,7 +28,7 @@
2828
provides RevealPathService with DBusSendRevealPathService;
2929
provides TrayMenuController with AppindicatorTrayMenuController;
3030
provides QuickAccessService with NautilusBookmarks, DolphinPlaces;
31-
provides UpdateService with FlatpakUpdater;
31+
provides UpdateMechanism with FlatpakUpdater;
3232

3333
opens org.cryptomator.linux.tray to org.cryptomator.integrations.api;
3434
opens org.cryptomator.linux.quickaccess to org.cryptomator.integrations.api;

src/main/java/org/cryptomator/linux/update/FlatpakUpdater.java

Lines changed: 136 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import org.cryptomator.integrations.common.DisplayName;
55
import org.cryptomator.integrations.common.OperatingSystem;
66
import org.cryptomator.integrations.common.Priority;
7-
import org.cryptomator.integrations.update.Progress;
8-
import org.cryptomator.integrations.update.ProgressListener;
97
import org.cryptomator.integrations.update.UpdateFailedException;
10-
import org.cryptomator.integrations.update.UpdateService;
8+
import org.cryptomator.integrations.update.UpdateMechanism;
9+
import org.cryptomator.integrations.update.UpdateProcess;
1110
import org.freedesktop.dbus.FileDescriptor;
1211
import org.freedesktop.dbus.exceptions.DBusException;
1312
import org.freedesktop.dbus.types.UInt32;
@@ -16,125 +15,188 @@
1615
import org.purejava.portal.FlatpakSpawnFlag;
1716
import org.purejava.portal.UpdatePortal;
1817
import org.purejava.portal.Util;
19-
import org.purejava.portal.rest.UpdateCheckerTask;
2018
import org.slf4j.Logger;
2119
import org.slf4j.LoggerFactory;
2220

21+
import java.io.IOException;
2322
import java.util.Collections;
2423
import java.util.List;
2524
import java.util.Map;
2625
import java.util.concurrent.CopyOnWriteArrayList;
26+
import java.util.concurrent.CountDownLatch;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.concurrent.atomic.AtomicBoolean;
2729

2830
@Priority(1000)
2931
@CheckAvailability
3032
@DisplayName("Update via Flatpak update")
3133
@OperatingSystem(OperatingSystem.Value.LINUX)
32-
public class FlatpakUpdater implements UpdateService, AutoCloseable {
34+
public class FlatpakUpdater implements UpdateMechanism {
3335

3436
private static final Logger LOG = LoggerFactory.getLogger(FlatpakUpdater.class);
3537
private static final String APP_NAME = "org.cryptomator.Cryptomator";
3638

37-
private final List<ProgressListener> progressListeners = new CopyOnWriteArrayList<>();
38-
3939
private final UpdatePortal portal;
40-
private Flatpak.UpdateMonitor updateMonitor;
4140

4241
public FlatpakUpdater() {
4342
this.portal = new UpdatePortal();
4443
portal.CreateUpdateMonitor(UpdatePortal.OPTIONS_DUMMY);
4544
}
4645

47-
@Override
46+
@CheckAvailability
4847
public boolean isSupported() {
4948
return portal.isAvailable();
5049
}
5150

5251
@Override
53-
public UpdateCheckerTask getLatestReleaseChecker() {
52+
public boolean isUpdateAvailable() {
53+
var cdl = new CountDownLatch(1);
5454
portal.setUpdateCheckerTaskFor(APP_NAME);
55-
return portal.getUpdateCheckerTaskFor(APP_NAME);
55+
var checkTask = portal.getUpdateCheckerTaskFor(APP_NAME);
56+
var updateAvailable = new AtomicBoolean(false);
57+
checkTask.setOnSucceeded(latestVersion -> {
58+
updateAvailable.set(true); // TODO: compare version strings before setting this to true
59+
cdl.countDown();
60+
});
61+
checkTask.setOnFailed(error -> {
62+
LOG.warn("Error while checking for updates.", error);
63+
cdl.countDown();
64+
});
65+
try {
66+
cdl.await();
67+
return updateAvailable.get();
68+
} catch (InterruptedException e) {
69+
checkTask.cancel();
70+
Thread.currentThread().interrupt();
71+
return false;
72+
}
5673
}
5774

5875
@Override
59-
public void triggerUpdate() throws UpdateFailedException {
60-
var monitor = getUpdateMonitor();
61-
portal.updateApp("x11:0", monitor, UpdatePortal.OPTIONS_DUMMY);
62-
}
76+
public UpdateProcess prepareUpdate() throws UpdateFailedException {
77+
var monitorPath = portal.CreateUpdateMonitor(UpdatePortal.OPTIONS_DUMMY);
78+
if (monitorPath == null) {
79+
throw new UpdateFailedException("Failed to create UpdateMonitor on DBus");
80+
}
6381

64-
@Override
65-
public long spawnApp() {
66-
var cwdPath = Util.stringToByteList(System.getProperty("user.dir"));
67-
List<List<Byte>> argv = List.of(
68-
Util.stringToByteList(APP_NAME));
69-
Map<UInt32, FileDescriptor> fds = Collections.emptyMap();
70-
Map<String, String> envs = Map.of();
71-
UInt32 flags = new UInt32(FlatpakSpawnFlag.LATEST_VERSION.getValue());
72-
Map<String, Variant<?>> options = UpdatePortal.OPTIONS_DUMMY;
73-
74-
return spawnApp(cwdPath, argv, fds, envs, flags, options).longValue();
82+
return new FlatpakUpdateProcess(portal.getUpdateMonitor(monitorPath.toString()));
7583
}
7684

77-
@Override
78-
public boolean doesRequireElevatedPermissions() {
79-
return false;
80-
}
85+
private class FlatpakUpdateProcess implements UpdateProcess {
8186

82-
@Override
83-
public void close() throws Exception {
84-
try {
85-
if (null != updateMonitor) {
86-
portal.cancelUpdateMonitor(updateMonitor);
87+
private final CountDownLatch latch = new CountDownLatch(1);
88+
private final Flatpak.UpdateMonitor monitor;
89+
private volatile double progress = 0.0;
90+
private volatile UpdateFailedException error;
91+
private AutoCloseable signalHandler;
92+
93+
private FlatpakUpdateProcess(Flatpak.UpdateMonitor monitor) {
94+
this.monitor = monitor;
95+
startUpdate();
96+
}
97+
98+
private void startUpdate() {
99+
try {
100+
this.signalHandler = portal.getDBusConnection().addSigHandler(Flatpak.UpdateMonitor.Progress.class, this::handleProgressSignal);
101+
} catch (DBusException e) {
102+
LOG.error("DBus error", e);
103+
latch.countDown();
104+
}
105+
portal.updateApp("x11:0", monitor, UpdatePortal.OPTIONS_DUMMY);
106+
}
107+
108+
private void handleProgressSignal(Flatpak.UpdateMonitor.Progress signal) {
109+
int status = ((UInt32) signal.info.get("status").getValue()).intValue();
110+
switch (status) {
111+
case 0 -> { // In progress
112+
Variant<?> progressVariant = signal.info.get("progress");
113+
if (progressVariant != null) {
114+
progress = ((UInt32) progressVariant.getValue()).doubleValue() / 100.0; // progress reported as int in range [0, 100]
115+
}
116+
}
117+
case 1 -> { // No update available
118+
error = new UpdateFailedException("No update available");
119+
latch.countDown();
120+
}
121+
case 2 -> { // Update complete
122+
progress = 1.0;
123+
latch.countDown();
124+
}
125+
case 3 -> { // Update failed
126+
error = new UpdateFailedException("Update preparation failed");
127+
latch.countDown();
128+
}
129+
default -> {
130+
error = new UpdateFailedException("Unknown update status " + status);
131+
latch.countDown();
132+
}
87133
}
88-
portal.close();
89-
} catch (Exception e) {
90-
LOG.error(e.toString(), e.getCause());
91134
}
92-
}
93135

94-
private synchronized Flatpak.UpdateMonitor getUpdateMonitor() {
95-
if (updateMonitor == null) {
96-
var updateMonitorPath = portal.CreateUpdateMonitor(UpdatePortal.OPTIONS_DUMMY);
97-
if (updateMonitorPath != null) {
98-
LOG.debug("UpdateMonitor successful created at {}", updateMonitorPath);
99-
updateMonitor = portal.getUpdateMonitor(updateMonitorPath.toString());
136+
private void stopReceivingSignals() {
137+
if (signalHandler != null) {
100138
try {
101-
portal.getDBusConnection().addSigHandler(Flatpak.UpdateMonitor.Progress.class, signal -> {
102-
notifyOnUpdateProceeds(signal);
103-
});
104-
} catch (DBusException e) {
105-
LOG.error(e.toString(), e.getCause());
139+
signalHandler.close();
140+
} catch (Exception e) {
141+
LOG.error("Failed to close signal handler", e);
106142
}
107-
} else {
108-
LOG.error("Failed to create UpdateMonitor on DBus");
143+
signalHandler = null;
109144
}
110145
}
111-
return updateMonitor;
112-
}
113146

114-
@Override
115-
public void addProgressListener(ProgressListener listener) {
116-
progressListeners.add(listener);
117-
}
147+
@Override
148+
public double preparationProgress() {
149+
return progress;
150+
}
118151

119-
@Override
120-
public void removeProgressListener(ProgressListener listener) {
121-
progressListeners.remove(listener);
122-
}
152+
@Override
153+
public void cancel() {
154+
portal.cancelUpdateMonitor(monitor);
155+
stopReceivingSignals();
156+
portal.close(); // TODO: is this right? belongs to parent class. update can not be retried afterwards. or should each process have its own portal instance?
157+
error = new UpdateFailedException("Update cancelled by user");
158+
}
123159

124-
private void notifyOnUpdateProceeds(Flatpak.UpdateMonitor.Progress signal) {
125-
long status = ((UInt32) signal.info.get("status").getValue()).longValue();
126-
long progress = 0;
127-
Variant<?> progressVariant = signal.info.get("progress");
128-
if (null != progressVariant) {
129-
progress = ((UInt32) progressVariant.getValue()).longValue();
160+
@Override
161+
public void await() throws InterruptedException {
162+
latch.await();
130163
}
131-
Progress p = new Progress(status, progress);
132-
for (ProgressListener listener : progressListeners) {
133-
listener.onProgress(p);
164+
165+
@Override
166+
public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
167+
return latch.await(timeout, unit);
168+
}
169+
170+
private boolean isDone() {
171+
try {
172+
return latch.await(0, TimeUnit.MILLISECONDS);
173+
} catch (InterruptedException e) {
174+
Thread.currentThread().interrupt();
175+
return false;
176+
}
134177
}
135-
}
136178

137-
private UInt32 spawnApp(List<Byte> cwdPath, List<List<Byte>> argv, Map<UInt32, FileDescriptor> fds, Map<String, String> envs, UInt32 flags, Map<String, Variant<?>> options) {
138-
return portal.Spawn(cwdPath, argv, fds, envs, flags, options);
179+
@Override
180+
public ProcessHandle applyUpdate() throws IllegalStateException, IOException {
181+
if (!isDone()) {
182+
throw new IllegalStateException("Update preparation is not complete");
183+
}
184+
stopReceivingSignals();
185+
if (error != null) {
186+
throw error;
187+
}
188+
189+
// spawn new Cryptomator process:
190+
var cwdPath = Util.stringToByteList(System.getProperty("user.dir"));
191+
List<List<Byte>> argv = List.of(
192+
Util.stringToByteList(APP_NAME));
193+
Map<UInt32, FileDescriptor> fds = Collections.emptyMap();
194+
Map<String, String> envs = Map.of();
195+
UInt32 flags = new UInt32(FlatpakSpawnFlag.LATEST_VERSION.getValue());
196+
Map<String, Variant<?>> options = UpdatePortal.OPTIONS_DUMMY;
197+
var pid = portal.Spawn(cwdPath, argv, fds, envs, flags, options).longValue();
198+
return ProcessHandle.of(pid).orElseThrow();
199+
}
139200
}
201+
140202
}

0 commit comments

Comments
 (0)