From 335a91d24a416ecf94a23d4ab93e8f622eaf3b94 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 2 Dec 2021 12:33:51 +0100 Subject: [PATCH 001/109] reset to snapshot version [ci skip] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ad640f72..55501fb5c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator cryptomator - 1.6.4 + 1.7.0-SNAPSHOT Cryptomator Desktop App From 085304ec0421653881a087b4d8834a15724ad6cb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 2 Dec 2021 17:16:01 +0100 Subject: [PATCH 002/109] set up SonarCloud --- .github/workflows/build.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ffc3070e..750b64826 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,8 +24,24 @@ jobs: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} cache: 'maven' + - name: Cache SonarCloud packages + uses: actions/cache@v2 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar - name: Build and Test - run: mvn -B clean install jacoco:report -Pcoverage,dependency-check + run: > + mvn -B verify + jacoco:report + org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + -Pcoverage,dependency-check + -Dsonar.projectKey=cryptomator_cryptomator + -Dsonar.organization=cryptomator + -Dsonar.host.url=https://sonarcloud.io + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - name: Upload code coverage report id: codacyCoverageReporter if: "github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'pr:safe')" From f277d4d21bbb8c536dff2c77efe72ceb77e88cd2 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 11:38:25 +0100 Subject: [PATCH 003/109] fix resource leak --- .../common/mountpoint/MountPointHelper.java | 10 ++++++---- src/main/java/org/cryptomator/ipc/Server.java | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java b/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java index 704f2f62d..fe64902bd 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java +++ b/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java @@ -66,9 +66,9 @@ class MountPointHelper { private void clearIrregularUnmountDebris(Path dirContainingMountPoints) { IOException cleanupFailed = new IOException("Cleanup failed"); - try { + try (var ds = Files.newDirectoryStream(dirContainingMountPoints)) { LOG.debug("Performing cleanup of mountpoint dir {}.", dirContainingMountPoints); - for (Path p : Files.newDirectoryStream(dirContainingMountPoints)) { + for (Path p : ds) { try { var attr = Files.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); if (attr.isOther() && attr.isDirectory()) { // yes, this is possible with windows junction points -.- @@ -113,8 +113,10 @@ class MountPointHelper { } private void ensureIsEmpty(Path dir) throws IOException { - if (Files.newDirectoryStream(dir).iterator().hasNext()) { - throw new DirectoryNotEmptyException(dir.toString()); + try (var ds = Files.newDirectoryStream(dir)) { + if (ds.iterator().hasNext()){ + throw new DirectoryNotEmptyException(dir.toString()); + } } } } diff --git a/src/main/java/org/cryptomator/ipc/Server.java b/src/main/java/org/cryptomator/ipc/Server.java index 6058a608f..770373681 100644 --- a/src/main/java/org/cryptomator/ipc/Server.java +++ b/src/main/java/org/cryptomator/ipc/Server.java @@ -7,9 +7,11 @@ import java.io.EOFException; import java.io.IOException; import java.net.StandardProtocolFamily; import java.net.UnixDomainSocketAddress; +import java.nio.channels.AlreadyBoundException; import java.nio.channels.AsynchronousCloseException; import java.nio.channels.ClosedChannelException; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.UnsupportedAddressTypeException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.Executor; @@ -29,10 +31,18 @@ class Server implements IpcCommunicator { public static Server create(Path socketPath) throws IOException { Files.createDirectories(socketPath.getParent()); var address = UnixDomainSocketAddress.of(socketPath); - var serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX); - serverSocketChannel.bind(address); - LOG.info("Spawning IPC server listening on socket {}", socketPath); - return new Server(serverSocketChannel, socketPath); + ServerSocketChannel ch = null; + try { + ch = ServerSocketChannel.open(StandardProtocolFamily.UNIX); + ch.bind(address); + LOG.info("Spawning IPC server listening on socket {}", socketPath); + return new Server(ch, socketPath); + } catch (IOException | AlreadyBoundException | UnsupportedAddressTypeException e) { + if (ch != null) { + ch.close(); + } + throw e; + } } @Override From 0fd6e5bbb0d275327f106f35271eba70b8e8054d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 3 Dec 2021 12:17:07 +0100 Subject: [PATCH 004/109] fixed code smells --- .../org/cryptomator/logging/LoggerModule.java | 10 +++++---- .../ui/common/UserInteractionLock.java | 21 ++++++++++++------- .../ui/fxapp/UpdateCheckerTask.java | 2 +- .../ui/stats/VaultStatisticsController.java | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/cryptomator/logging/LoggerModule.java b/src/main/java/org/cryptomator/logging/LoggerModule.java index 4866655e3..5031fe3be 100644 --- a/src/main/java/org/cryptomator/logging/LoggerModule.java +++ b/src/main/java/org/cryptomator/logging/LoggerModule.java @@ -79,8 +79,9 @@ public class LoggerModule { @Singleton @Named("fileAppender") static Appender provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { - if (environment.getLogDir().isPresent()) { - Path logDir = environment.getLogDir().get(); + var optionalLogDir = environment.getLogDir(); + if (optionalLogDir.isPresent()) { + Path logDir = optionalLogDir.get(); RollingFileAppender appender = new RollingFileAppender<>(); appender.setContext(context); appender.setFile(logDir.resolve(LOGFILE_NAME).toString()); @@ -110,9 +111,10 @@ public class LoggerModule { @Singleton @Named("upgradeAppender") static Appender provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) { - if (environment.getLogDir().isPresent()) { + var optionalLogDir = environment.getLogDir(); + if (optionalLogDir.isPresent()) { FileAppender appender = new FileAppender<>(); - appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString()); + appender.setFile(optionalLogDir.get().resolve(UPGRADE_FILENAME).toString()); appender.setContext(context); appender.setEncoder(encoder); appender.start(); diff --git a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java b/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java index 4eba62552..12c394533 100644 --- a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java +++ b/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java @@ -4,30 +4,35 @@ import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.SimpleBooleanProperty; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -public class UserInteractionLock { +public class UserInteractionLock> { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private final BooleanProperty awaitingInteraction = new SimpleBooleanProperty(); - private volatile E state; + private final AtomicBoolean interacted = new AtomicBoolean(); + private final AtomicReference state; public UserInteractionLock(E initialValue) { - this.state = initialValue; + this.state = new AtomicReference<>(initialValue); } public synchronized void reset(E value) { - this.state = value; + state.set(value); + interacted.set(false); } public void interacted(E result) { assert Platform.isFxApplicationThread(); lock.lock(); try { - state = result; + state.set(result); + interacted.set(true); awaitingInteraction.set(false); condition.signal(); } finally { @@ -40,8 +45,10 @@ public class UserInteractionLock { lock.lock(); try { Platform.runLater(() -> awaitingInteraction.set(true)); - condition.await(); - return state; + while (!interacted.get()) { + condition.await(); + } + return state.get(); } finally { lock.unlock(); } diff --git a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java index d9de7f2da..032148cea 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java +++ b/src/main/java/org/cryptomator/ui/fxapp/UpdateCheckerTask.java @@ -23,7 +23,7 @@ public class UpdateCheckerTask extends Task { private static final Logger LOG = LoggerFactory.getLogger(UpdateCheckerTask.class); - private static final long MAX_RESPONSE_SIZE = 10 * 1024; // 10kb should be sufficient. protect against flooding + private static final long MAX_RESPONSE_SIZE = 10L * 1024; // 10kb should be sufficient. protect against flooding private static final Gson GSON = new GsonBuilder().setLenient().create(); private final HttpClient httpClient; diff --git a/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java b/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java index a3c430946..56729f1fe 100644 --- a/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java +++ b/src/main/java/org/cryptomator/ui/stats/VaultStatisticsController.java @@ -127,10 +127,10 @@ public class VaultStatisticsController implements FxController { encryptedBytesWrite.getData().add(new Data<>(currentStep, encBytes)); // adjust ranges: - readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS); + readChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0); readChartXAxis.setUpperBound(currentStep); readChartYAxis.setUpperBound(allTimeMax); - writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS); + writeChartXAxis.setLowerBound(currentStep - IO_SAMPLING_STEPS * 1.0); writeChartXAxis.setUpperBound(currentStep); writeChartYAxis.setUpperBound(allTimeMax); } From 46a3a4fc119948337b8a5cd67a7dbe9d7e6e370e Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Thu, 9 Dec 2021 23:10:45 +0100 Subject: [PATCH 005/109] Added error message if user tries to mount to occupied drive. Fixes #1888 --- .../mountpoint/CustomDriveLetterChooser.java | 12 +++++++++ .../mountpoint/CustomMountPointChooser.java | 2 +- .../TemporaryMountPointChooser.java | 2 +- .../common/vaults/DokanyVolume.java | 2 +- .../cryptomator/common/vaults/FuseVolume.java | 13 ++++++--- .../common/vaults/MountPointRequirement.java | 5 ++++ .../UnlockInvalidMountPointController.java | 27 ++++++++++++++----- .../cryptomator/ui/unlock/UnlockWorkflow.java | 10 +++++-- .../fxml/unlock_invalid_mount_point.fxml | 5 ++-- src/main/resources/i18n/strings.properties | 1 + 10 files changed, 62 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java index 1a42aa5ad..02f75d4a1 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomDriveLetterChooser.java @@ -5,6 +5,9 @@ import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.Volume; import javax.inject.Inject; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; @@ -27,4 +30,13 @@ class CustomDriveLetterChooser implements MountPointChooser { public Optional chooseMountPoint(Volume caller) { return this.vaultSettings.getWinDriveLetter().map(letter -> letter.charAt(0) + ":\\").map(Paths::get); } + + @Override + public boolean prepare(Volume caller, Path driveLetter) throws InvalidMountPointException { + if (!Files.notExists(driveLetter, LinkOption.NOFOLLOW_LINKS)) { + //Drive already exists OR can't be determined + throw new InvalidMountPointException(new FileAlreadyExistsException(driveLetter.toString())); + } + return false; + } } diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 5f1a7fedd..a78564db4 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -56,7 +56,7 @@ class CustomMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } default -> { - //Currently the case for "PARENT_OPT_MOUNT_POINT" + //Currently the case for "NO_PARENT_NO_MOUNT_POINT, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } diff --git a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java index eb1d8d0b1..b119ff084 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java @@ -65,7 +65,7 @@ class TemporaryMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } default -> { - //Currently the case for "PARENT_OPT_MOUNT_POINT" + //Currently the case for "NO_PARENT_NO_MOUNT_POINT, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } diff --git a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java index c998761d0..64bd41edf 100644 --- a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java @@ -86,7 +86,7 @@ public class DokanyVolume extends AbstractVolume { @Override public MountPointRequirement getMountPointRequirement() { - return MountPointRequirement.EMPTY_MOUNT_POINT; + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.NO_PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java index a1579fdaf..e3b0d2ed5 100644 --- a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java @@ -4,6 +4,7 @@ import com.google.common.collect.Iterators; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.mountpoint.InvalidMountPointException; import org.cryptomator.common.mountpoint.MountPointChooser; +import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; import org.cryptomator.cryptofs.CryptoFileSystem; import org.cryptomator.frontend.fuse.mount.EnvironmentVariables; @@ -28,11 +29,14 @@ public class FuseVolume extends AbstractVolume { private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class); private static final Pattern NON_WHITESPACE_OR_QUOTED = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); // Thanks to https://stackoverflow.com/a/366532 + private final VaultSettings vaultSettings; + private Mount mount; @Inject - public FuseVolume(@Named("orderedMountPointChoosers") Iterable choosers) { + public FuseVolume(VaultSettings vaultSettings, @Named("orderedMountPointChoosers") Iterable choosers) { super(choosers); + this.vaultSettings = vaultSettings; } @Override @@ -50,7 +54,7 @@ public class FuseVolume extends AbstractVolume { .withFileNameTranscoder(mounter.defaultFileNameTranscoder()) // .build(); this.mount = mounter.mount(root, envVars, onExitAction); - } catch ( FuseMountException | FuseNotSupportedException e) { + } catch (FuseMountException | FuseNotSupportedException e) { throw new VolumeException("Unable to mount Filesystem", e); } } @@ -119,7 +123,10 @@ public class FuseVolume extends AbstractVolume { @Override public MountPointRequirement getMountPointRequirement() { - return SystemUtils.IS_OS_WINDOWS ? MountPointRequirement.PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT; + if (!SystemUtils.IS_OS_WINDOWS) { + return MountPointRequirement.EMPTY_MOUNT_POINT; + } + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.NO_PARENT_NO_MOUNT_POINT : MountPointRequirement.PARENT_NO_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java index 84a798e59..b7510e811 100644 --- a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java +++ b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java @@ -6,6 +6,11 @@ package org.cryptomator.common.vaults; */ public enum MountPointRequirement { + /** + * There must not be a parent folder and the actual Mountpoint must not exist. + */ + NO_PARENT_NO_MOUNT_POINT, + /** * No Mountpoint on the local filesystem required. (e.g. WebDAV) */ diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java index fd85db988..f84842807 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java @@ -1,5 +1,6 @@ package org.cryptomator.ui.unlock; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; @@ -32,12 +33,24 @@ public class UnlockInvalidMountPointController implements FxController { return vault.getVaultSettings().getCustomMountPath().orElse("AUTO"); } - public boolean getMustExist() { - MountPointRequirement requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement(); - assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible - assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) - - return requirement == MountPointRequirement.EMPTY_MOUNT_POINT; + public boolean getNotExisting() { + return getMountPointRequirement() == MountPointRequirement.EMPTY_MOUNT_POINT; } -} + public boolean getExisting() { + return getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT; + } + + public boolean getDriveLetterOccupied() { + return getMountPointRequirement() == MountPointRequirement.NO_PARENT_NO_MOUNT_POINT; + } + + private MountPointRequirement getMountPointRequirement() { + var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement(); + assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible + assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) + assert requirement != MountPointRequirement.NO_PARENT_NO_MOUNT_POINT || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows + + return requirement; + } +} \ No newline at end of file diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 073258d80..db7b6f7d9 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -2,6 +2,7 @@ package org.cryptomator.ui.unlock; import com.google.common.base.Throwables; import dagger.Lazy; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.mountpoint.InvalidMountPointException; import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Vault; @@ -79,9 +80,10 @@ public class UnlockWorkflow extends Task { } private void handleInvalidMountPoint(InvalidMountPointException impExc) { - MountPointRequirement requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement(); + var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement(); assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) + assert requirement != MountPointRequirement.NO_PARENT_NO_MOUNT_POINT || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows Throwable cause = impExc.getCause(); // TODO: apply https://openjdk.java.net/jeps/8213076 in future JDK versions @@ -93,7 +95,11 @@ public class UnlockWorkflow extends Task { } showInvalidMountPointScene(); } else if (cause instanceof FileAlreadyExistsException) { - LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); + if (requirement == MountPointRequirement.NO_PARENT_NO_MOUNT_POINT) { + LOG.error("Unlock failed. Drive Letter already occupied: {}", cause.getMessage()); + } else { + LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); + } showInvalidMountPointScene(); } else if (cause instanceof DirectoryNotEmptyException) { LOG.error("Unlock failed. Mountpoint not an empty directory: {}", cause.getMessage()); diff --git a/src/main/resources/fxml/unlock_invalid_mount_point.fxml b/src/main/resources/fxml/unlock_invalid_mount_point.fxml index 253ff5704..062981304 100644 --- a/src/main/resources/fxml/unlock_invalid_mount_point.fxml +++ b/src/main/resources/fxml/unlock_invalid_mount_point.fxml @@ -29,8 +29,9 @@ - - + + + diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 7b17fa791..38b8128a2 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -115,6 +115,7 @@ unlock.error.heading=Unable to unlock vault ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist. unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing. +unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already occupied. # Lock ## Force From 0bece0f59124231176473c790fdb157e635c079c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 13:56:59 +0100 Subject: [PATCH 006/109] move passphrase entry to subcomponent and use CompletableFuture instead of UserInteractionLock + AtomicReference --- .../org/cryptomator/ui/common/FxmlFile.java | 2 +- .../ui/keyloading/KeyLoadingModule.java | 2 +- .../MasterkeyFileLoadingFinisher.java | 62 -------------- .../MasterkeyFileLoadingModule.java | 39 +-------- .../MasterkeyFileLoadingStrategy.java | 81 ++++++++++++------- .../PassphraseEntryComponent.java | 35 ++++++++ .../PassphraseEntryController.java | 80 +++++++----------- .../masterkeyfile/PassphraseEntryModule.java | 41 ++++++++++ .../masterkeyfile/PassphraseEntryResult.java | 6 ++ .../masterkeyfile/PassphraseEntryScoped.java | 13 +++ 10 files changed, 179 insertions(+), 182 deletions(-) delete mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index b8d5bbff0..bc952f9d1 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -42,7 +42,7 @@ public enum FxmlFile { this.ressourcePathString = ressourcePathString; } - String getRessourcePathString() { + public String getRessourcePathString() { return ressourcePathString; } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java index bff757b1a..616e7e5e0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/KeyLoadingModule.java @@ -26,7 +26,7 @@ abstract class KeyLoadingModule { @Provides @KeyLoading @KeyLoadingScoped - static KeyLoadingStrategy provideKeyLoaderProvider(@KeyLoading Vault vault, Map> strategies) { + static KeyLoadingStrategy provideKeyLoadingStrategy(@KeyLoading Vault vault, Map> strategies) { try { String scheme = vault.getVaultConfigCache().get().getKeyId().getScheme(); var fallback = KeyLoadingStrategy.failed(new IllegalArgumentException("Unsupported key id " + scheme)); diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java deleted file mode 100644 index 44d7ebfb0..000000000 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingFinisher.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.cryptomator.ui.keyloading.masterkeyfile; - -import org.cryptomator.common.keychain.KeychainManager; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.integrations.keychain.KeychainAccessException; -import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import java.nio.CharBuffer; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -@KeyLoadingScoped -class MasterkeyFileLoadingFinisher { - - private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingFinisher.class); - - private final Vault vault; - private final Optional storedPassword; - private final AtomicReference enteredPassword; - private final AtomicBoolean shouldSavePassword; - private final KeychainManager keychain; - - @Inject - MasterkeyFileLoadingFinisher(@KeyLoading Vault vault, @Named("savedPassword") Optional storedPassword, AtomicReference enteredPassword, @Named("savePassword") AtomicBoolean shouldSavePassword, KeychainManager keychain) { - this.vault = vault; - this.storedPassword = storedPassword; - this.enteredPassword = enteredPassword; - this.shouldSavePassword = shouldSavePassword; - this.keychain = keychain; - } - - public void cleanup(boolean successfullyUnlocked) { - if (successfullyUnlocked && shouldSavePassword.get()) { - savePasswordToSystemkeychain(); - } - wipePassword(storedPassword.orElse(null)); - wipePassword(enteredPassword.getAndSet(null)); - } - - private void savePasswordToSystemkeychain() { - if (keychain.isSupported()) { - try { - keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(enteredPassword.get())); - } catch (KeychainAccessException e) { - LOG.error("Failed to store passphrase in system keychain.", e); - } - } - } - - private void wipePassword(char[] pw) { - if (pw != null) { - Arrays.fill(pw, ' '); - } - } -} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 901eacfb9..7f55de898 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -25,30 +25,18 @@ import javax.inject.Named; import javafx.scene.Scene; import java.nio.file.Path; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -@Module(subcomponents = {ForgetPasswordComponent.class}) +@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class}) public abstract class MasterkeyFileLoadingModule { private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class); - public enum PasswordEntry { - PASSWORD_ENTERED, - CANCELED - } - public enum MasterkeyFileProvision { MASTERKEYFILE_PROVIDED, CANCELED } - @Provides - @KeyLoadingScoped - static UserInteractionLock providePasswordEntryLock() { - return new UserInteractionLock<>(null); - } - @Provides @KeyLoadingScoped static UserInteractionLock provideMasterkeyFileProvisionLock() { @@ -77,26 +65,6 @@ public abstract class MasterkeyFileLoadingModule { return new AtomicReference<>(); } - @Provides - @KeyLoadingScoped - static AtomicReference providePassword(@Named("savedPassword") Optional storedPassword) { - return new AtomicReference<>(storedPassword.orElse(null)); - } - - @Provides - @Named("savePassword") - @KeyLoadingScoped - static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional storedPassword) { - return new AtomicBoolean(storedPassword.isPresent()); - } - - @Provides - @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) - @KeyLoadingScoped - static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); - } - @Provides @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) @KeyLoadingScoped @@ -104,11 +72,6 @@ public abstract class MasterkeyFileLoadingModule { return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); } - @Binds - @IntoMap - @FxControllerKey(PassphraseEntryController.class) - abstract FxController bindUnlockController(PassphraseEntryController controller); - @Binds @IntoMap @FxControllerKey(SelectMasterkeyFileController.class) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 1fa7dd986..2ee17a744 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -2,12 +2,14 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import com.google.common.base.Preconditions; import dagger.Lazy; +import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.common.BackupHelper; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; +import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; @@ -17,6 +19,7 @@ import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.unlock.UnlockCancelledException; import javax.inject.Inject; +import javax.inject.Named; import javafx.application.Platform; import javafx.scene.Scene; import javafx.stage.Stage; @@ -26,6 +29,10 @@ import java.net.URI; import java.nio.CharBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; @KeyLoading @@ -36,28 +43,28 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final Vault vault; private final MasterkeyFileAccess masterkeyFileAccess; private final Stage window; - private final Lazy passphraseEntryScene; private final Lazy selectMasterkeyFileScene; - private final UserInteractionLock passwordEntryLock; + private final PassphraseEntryComponent.Builder passphraseEntry; private final UserInteractionLock masterkeyFileProvisionLock; - private final AtomicReference password; private final AtomicReference filePath; - private final MasterkeyFileLoadingFinisher finisher; + private final KeychainManager keychain; - private boolean wrongPassword; + private char[] passphrase; + private boolean savePassphrase; + private boolean wrongPassphrase; @Inject - public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) Lazy passphraseEntryScene, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, UserInteractionLock passwordEntryLock, UserInteractionLock masterkeyFileProvisionLock, AtomicReference password, AtomicReference filePath, MasterkeyFileLoadingFinisher finisher) { + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, UserInteractionLock masterkeyFileProvisionLock, AtomicReference filePath, KeychainManager keychain) { this.vault = vault; this.masterkeyFileAccess = masterkeyFileAccess; this.window = window; - this.passphraseEntryScene = passphraseEntryScene; this.selectMasterkeyFileScene = selectMasterkeyFileScene; - this.passwordEntryLock = passwordEntryLock; + this.passphraseEntry = passphraseEntry; this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; - this.password = password; this.filePath = filePath; - this.finisher = finisher; + this.keychain = keychain; + this.passphrase = savedPassphrase.orElse(null); + this.savePassphrase = savedPassphrase.isPresent(); } @Override @@ -68,8 +75,10 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { if (!Files.exists(filePath)) { filePath = getAlternateMasterkeyFilePath(); } - CharSequence passphrase = getPassphrase(); - var masterkey = masterkeyFileAccess.load(filePath, passphrase); + if (passphrase == null) { + askForPassphrase(); + } + var masterkey = masterkeyFileAccess.load(filePath, CharBuffer.wrap(passphrase)); //backup if (filePath.startsWith(vault.getPath())) { try { @@ -90,8 +99,8 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @Override public boolean recoverFromException(MasterkeyLoadingFailedException exception) { if (exception instanceof InvalidPassphraseException) { - this.wrongPassword = true; - password.set(null); + this.wrongPassphrase = true; + this.passphrase = null; return true; // reattempting key load } else { return false; // nothing we can do @@ -100,7 +109,20 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @Override public void cleanup(boolean unlockedSuccessfully) { - finisher.cleanup(unlockedSuccessfully); + if (unlockedSuccessfully && savePassphrase) { + savePasswordToSystemkeychain(passphrase); + } + Arrays.fill(passphrase, '\0'); + } + + private void savePasswordToSystemkeychain(char[] passphrase) { + if (keychain.isSupported()) { + try { + keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(passphrase)); + } catch (KeychainAccessException e) { + LOG.error("Failed to store passphrase in system keychain.", e); + } + } } private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException { @@ -129,21 +151,10 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { return masterkeyFileProvisionLock.awaitInteraction(); } - private CharSequence getPassphrase() throws UnlockCancelledException, InterruptedException { - if (password.get() == null) { - return switch (askForPassphrase()) { - case PASSWORD_ENTERED -> CharBuffer.wrap(password.get()); - case CANCELED -> throw new UnlockCancelledException("Password entry cancelled."); - }; - } else { - // e.g. pre-filled from keychain or previous unlock attempt - return CharBuffer.wrap(password.get()); - } - } - - private MasterkeyFileLoadingModule.PasswordEntry askForPassphrase() throws InterruptedException { + private void askForPassphrase() throws InterruptedException { + var comp = passphraseEntry.savedPassword(passphrase).build(); Platform.runLater(() -> { - window.setScene(passphraseEntryScene.get()); + window.setScene(comp.passphraseEntryScene()); window.show(); Window owner = window.getOwner(); if (owner != null) { @@ -152,11 +163,19 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { } else { window.centerOnScreen(); } - if (wrongPassword) { + if (wrongPassphrase) { Animations.createShakeWindowAnimation(window).play(); } }); - return passwordEntryLock.awaitInteraction(); + try { + var result = comp.result().get(); + this.passphrase = result.passphrase(); + this.savePassphrase = result.savePassphrase(); + } catch (CancellationException e) { + throw new UnlockCancelledException("Password entry cancelled."); + } catch (ExecutionException e) { + throw new MasterkeyLoadingFailedException("Failed to ask for password.", e); + } } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java new file mode 100644 index 000000000..637ffd5c6 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java @@ -0,0 +1,35 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.BindsInstance; +import dagger.Subcomponent; +import org.cryptomator.common.Nullable; +import org.cryptomator.ui.common.Animations; + +import javax.inject.Named; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.stage.Window; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +@PassphraseEntryScoped +@Subcomponent(modules = {PassphraseEntryModule.class}) +public interface PassphraseEntryComponent { + + @PassphraseEntryScoped + Scene passphraseEntryScene(); + + @PassphraseEntryScoped + CompletableFuture result(); + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") char[] savedPassword); + + PassphraseEntryComponent build(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java index f6ce79e51..d048cb776 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java @@ -1,16 +1,13 @@ package org.cryptomator.ui.keyloading.masterkeyfile; +import org.cryptomator.common.Nullable; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.common.WeakBindings; -import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.PasswordEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +18,8 @@ import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; +import javafx.application.Platform; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; import javafx.beans.binding.StringBinding; import javafx.beans.property.BooleanProperty; @@ -37,33 +34,27 @@ import javafx.scene.transform.Translate; import javafx.stage.Stage; import javafx.stage.WindowEvent; import javafx.util.Duration; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CompletableFuture; -@KeyLoadingScoped +@PassphraseEntryScoped public class PassphraseEntryController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(PassphraseEntryController.class); private final Stage window; private final Vault vault; - private final AtomicReference password; - private final AtomicBoolean savePassword; - private final Optional savedPassword; - private final UserInteractionLock passwordEntryLock; + private final CompletableFuture result; + private final char[] savedPassword; private final ForgetPasswordComponent.Builder forgetPassword; private final KeychainManager keychain; - private final ObjectBinding unlockButtonContentDisplay; - private final BooleanBinding userInteractionDisabled; - private final BooleanProperty unlockButtonDisabled; private final StringBinding vaultName; + private final BooleanProperty unlockInProgress = new SimpleBooleanProperty(); + private final ObjectBinding unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, unlockInProgress); + private final BooleanProperty unlockButtonDisabled = new SimpleBooleanProperty(); /* FXML */ public NiceSecurePasswordField passwordField; public CheckBox savePasswordCheckbox; - public FontAwesome5IconView unlockInProgressView; public ImageView face; public ImageView leftArm; public ImageView rightArm; @@ -72,29 +63,25 @@ public class PassphraseEntryController implements FxController { public Animation unlockAnimation; @Inject - public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, AtomicReference password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional savedPassword, UserInteractionLock passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { + public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, @Nullable @Named("savedPassword") char[] savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { this.window = window; this.vault = vault; - this.password = password; - this.savePassword = savePassword; + this.result = result; this.savedPassword = savedPassword; - this.passwordEntryLock = passwordEntryLock; this.forgetPassword = forgetPassword; this.keychain = keychain; - this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction()); - this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not(); - this.unlockButtonDisabled = new SimpleBooleanProperty(); this.vaultName = WeakBindings.bindString(vault.displayNameProperty()); - this.window.setOnHiding(this::windowClosed); + window.setOnHiding(this::windowClosed); + result.whenCompleteAsync((r, t) -> unlockInProgress.set(false), Platform::runLater); } @FXML public void initialize() { - savePasswordCheckbox.setSelected(savedPassword.isPresent()); - if (password.get() != null) { - passwordField.setPassword(password.get()); + if (savedPassword != null) { + savePasswordCheckbox.setSelected(true); + passwordField.setPassword(savedPassword); } - unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty())); + unlockButtonDisabled.bind(unlockInProgress.or(passwordField.textProperty().isEmpty())); var leftArmTranslation = new Translate(24, 0); var leftArmRotation = new Rotate(60, 16, 30, 0); @@ -132,7 +119,7 @@ public class PassphraseEntryController implements FxController { new KeyFrame(Duration.millis(1000), faceVisible) // ); - passwordEntryLock.awaitingInteraction().addListener(observable -> stopUnlockAnimation()); + result.whenCompleteAsync((r, t) -> stopUnlockAnimation()); } @FXML @@ -141,26 +128,20 @@ public class PassphraseEntryController implements FxController { } private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, mark this workflow as cancelled: - if (passwordEntryLock.awaitingInteraction().get()) { - LOG.debug("Unlock canceled by user."); - passwordEntryLock.interacted(PasswordEntry.CANCELED); - } + LOG.debug("Unlock canceled by user."); + result.cancel(true); } @FXML public void unlock() { LOG.trace("UnlockController.unlock()"); + unlockInProgress.set(true); CharSequence pwFieldContents = passwordField.getCharacters(); - char[] newPw = new char[pwFieldContents.length()]; + char[] pw = new char[pwFieldContents.length()]; for (int i = 0; i < pwFieldContents.length(); i++) { - newPw[i] = pwFieldContents.charAt(i); + pw[i] = pwFieldContents.charAt(i); } - char[] oldPw = password.getAndSet(newPw); - if (oldPw != null) { - Arrays.fill(oldPw, ' '); - } - passwordEntryLock.interacted(PasswordEntry.PASSWORD_ENTERED); + result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected())); startUnlockAnimation(); } @@ -184,8 +165,7 @@ public class PassphraseEntryController implements FxController { @FXML private void didClickSavePasswordCheckbox() { - savePassword.set(savePasswordCheckbox.isSelected()); - if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) { + if (!savePasswordCheckbox.isSelected() && savedPassword != null) { forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten)); } } @@ -205,15 +185,15 @@ public class PassphraseEntryController implements FxController { } public ContentDisplay getUnlockButtonContentDisplay() { - return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT; + return unlockInProgress.get() ? ContentDisplay.LEFT : ContentDisplay.TEXT_ONLY; } - public BooleanBinding userInteractionDisabledProperty() { - return userInteractionDisabled; + public ReadOnlyBooleanProperty userInteractionDisabledProperty() { + return unlockInProgress; } public boolean isUserInteractionDisabled() { - return userInteractionDisabled.get(); + return unlockInProgress.get(); } public ReadOnlyBooleanProperty unlockButtonDisabledProperty() { @@ -227,4 +207,6 @@ public class PassphraseEntryController implements FxController { public boolean isKeychainAccessAvailable() { return keychain.isSupported(); } + + } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java new file mode 100644 index 000000000..aec32e5e6 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -0,0 +1,41 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@Module +abstract class PassphraseEntryModule { + + @Provides + @PassphraseEntryScoped + static CompletableFuture provideResult() { + return new CompletableFuture<>(); + } + + @Provides + @PassphraseEntryScoped + static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + // TODO: simplify FxmlLoaderFactory + try { + var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_ENTER_PASSWORD.getRessourcePathString()); + var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); + Parent root = loader.load(); + return sceneFactory.apply(root); + } catch (IOException e) { + throw new UncheckedIOException("Failed to load UnlockScene", e); + } + } + + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java new file mode 100644 index 000000000..2c55c203e --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java @@ -0,0 +1,6 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +// TODO needs to be public due to Dagger -.- +public record PassphraseEntryResult(char[] passphrase, boolean savePassphrase) { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java new file mode 100644 index 000000000..a077bcf81 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface PassphraseEntryScoped { + +} From 85a5146d4bce1ea6671913f3448ad93f30a92c5f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 13:59:10 +0100 Subject: [PATCH 007/109] wipe pw before losing reference [ci skip] --- .../keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 2ee17a744..3a5c7548b 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -100,6 +100,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { public boolean recoverFromException(MasterkeyLoadingFailedException exception) { if (exception instanceof InvalidPassphraseException) { this.wrongPassphrase = true; + Arrays.fill(passphrase, '\0'); this.passphrase = null; return true; // reattempting key load } else { From 6403991bad831fd51aeecbe50d22f31b88cbca19 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Thu, 16 Dec 2021 14:03:15 +0100 Subject: [PATCH 008/109] Update release.yml [ci skip] --- .github/workflows/release.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39b6ce77d..92c2ba49f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -612,14 +612,18 @@ jobs: *.asc *.dmg *.msi - body: | + body: |- :construction: Work in Progress - ## What's new + ## What's New ## Bugfixes ## Misc + --- - :scroll: A complete list of closed issues is available [here](LINK) + + :scroll: A complete list of closed issues is available [here](LINK). + --- + :floppy_disk: SHA-256 checksums of release artifacts: ``` ${{ env.SHA256_SUMS }} From d72d9f533c0ed6a8dd6c67ed628ccf841f31ba38 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 16:41:07 +0100 Subject: [PATCH 009/109] Replace CharBuffer and char[] instances by destroyable Passphrase --- .../org/cryptomator/common/Passphrase.java | 115 ++++++++++++++++ .../ui/controls/NiceSecurePasswordField.java | 4 +- .../ui/controls/SecurePasswordField.java | 6 +- .../MasterkeyFileLoadingStrategy.java | 19 +-- .../PassphraseEntryComponent.java | 8 +- .../PassphraseEntryController.java | 10 +- .../masterkeyfile/PassphraseEntryResult.java | 4 +- .../cryptomator/common/PassphraseTest.java | 130 ++++++++++++++++++ 8 files changed, 270 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/cryptomator/common/Passphrase.java create mode 100644 src/test/java/org/cryptomator/common/PassphraseTest.java diff --git a/src/main/java/org/cryptomator/common/Passphrase.java b/src/main/java/org/cryptomator/common/Passphrase.java new file mode 100644 index 000000000..d57252507 --- /dev/null +++ b/src/main/java/org/cryptomator/common/Passphrase.java @@ -0,0 +1,115 @@ +package org.cryptomator.common; + +import org.cryptomator.cryptolib.common.MessageDigestSupplier; + +import javax.security.auth.Destroyable; +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * A destroyable CharSequence. + */ +public class Passphrase implements Destroyable, CharSequence { + + private final char[] data; + private final int offset; + private final int length; + private boolean destroyed; + + /** + * Wraps (doesn't copy) the given data. + * + * @param data The wrapped data. Any changes to this will be reflected in this passphrase + */ + public Passphrase(char[] data) { + this(data, 0, data.length); + } + + /** + * Wraps (doesn't copy) a subarray of the given data. + * + * @param data The wrapped data. Any changes to this will be reflected in this passphrase + * @param offset The subarray offset, i.e. the first character of this passphrase + * @param length The subarray length, i.e. the length of this passphrase + */ + public Passphrase(char[] data, int offset, int length) { + if (offset < 0 || length < 0 || offset + length > data.length) { + throw new IndexOutOfBoundsException("[%1$d %1$d + %2$d[ not within [0, %3$d[".formatted(offset, length, data.length)); + } + this.data = data; + this.offset = offset; + this.length = length; + } + + public static Passphrase copyOf(CharSequence cs) { + char[] result = new char[cs.length()]; + for (int i = 0; i < cs.length(); i++) { + result[i] = cs.charAt(i); + } + return new Passphrase(result); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Passphrase that = (Passphrase) o; + // time-constant comparison + int diff = 0; + for (int i = 0; i < length; i++) { + diff |= charAt(i) ^ that.charAt(i); + } + return diff == 0; + } + + @Override + public int hashCode() { + // TODO: do we really need to a secure hashcode? toString leaks the pw anyway + var md = MessageDigestSupplier.SHA256.get(); + ByteBuffer buf = ByteBuffer.allocate(Character.BYTES * length); + for (int i = 0; i < length; i++) { + char c = charAt(i); + buf.putChar(i * Character.BYTES, c); + } + buf.flip(); + md.update(buf); + return Arrays.hashCode(md.digest()); + } + + @Override + public String toString() { + return new String(data, offset, length); + } + + @Override + public int length() { + return length; + } + + @Override + public char charAt(int index) { + if (index < 0 || index >= length) { + throw new IndexOutOfBoundsException("%d not within [0, %d[".formatted(index, length)); + } + return data[offset + index]; + } + + @Override + public Passphrase subSequence(int start, int end) { + if (start < 0 || end < 0 || end > length || start > end) { + throw new IndexOutOfBoundsException("[%d, %d[ not within [0, %d[".formatted(start, end, length)); + } + return new Passphrase(Arrays.copyOfRange(data, offset + start, offset + end)); + } + + @Override + public boolean isDestroyed() { + return destroyed; + } + + @Override + public void destroy() { + Arrays.fill(data, offset, offset + length, '\0'); + destroyed = true; + } +} diff --git a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java index 4a4e43fff..4d09707b9 100644 --- a/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java +++ b/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java @@ -1,5 +1,7 @@ package org.cryptomator.ui.controls; +import org.cryptomator.common.Passphrase; + import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.StringProperty; @@ -82,7 +84,7 @@ public class NiceSecurePasswordField extends StackPane { return passwordField.textProperty(); } - public CharSequence getCharacters() { + public Passphrase getCharacters() { return passwordField.getCharacters(); } diff --git a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java index 0290f512d..66df79394 100644 --- a/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java +++ b/src/main/java/org/cryptomator/ui/controls/SecurePasswordField.java @@ -9,6 +9,7 @@ package org.cryptomator.ui.controls; import com.google.common.base.Strings; +import org.cryptomator.common.Passphrase; import javafx.application.Platform; import javafx.beans.NamedArg; @@ -28,7 +29,6 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; -import java.nio.CharBuffer; import java.text.Normalizer; import java.text.Normalizer.Form; import java.util.Arrays; @@ -203,8 +203,8 @@ public class SecurePasswordField extends TextField { * @see #wipe() */ @Override - public CharSequence getCharacters() { - return CharBuffer.wrap(content, 0, length); + public Passphrase getCharacters() { + return new Passphrase(content, 0, length); } /** diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 3a5c7548b..a22903973 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -13,6 +13,7 @@ import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.common.Passphrase; import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; @@ -26,10 +27,8 @@ import javafx.stage.Stage; import javafx.stage.Window; import java.io.IOException; import java.net.URI; -import java.nio.CharBuffer; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -49,7 +48,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final AtomicReference filePath; private final KeychainManager keychain; - private char[] passphrase; + private Passphrase passphrase; private boolean savePassphrase; private boolean wrongPassphrase; @@ -63,7 +62,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; this.filePath = filePath; this.keychain = keychain; - this.passphrase = savedPassphrase.orElse(null); + this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null); this.savePassphrase = savedPassphrase.isPresent(); } @@ -78,7 +77,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { if (passphrase == null) { askForPassphrase(); } - var masterkey = masterkeyFileAccess.load(filePath, CharBuffer.wrap(passphrase)); + var masterkey = masterkeyFileAccess.load(filePath, passphrase); //backup if (filePath.startsWith(vault.getPath())) { try { @@ -100,7 +99,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { public boolean recoverFromException(MasterkeyLoadingFailedException exception) { if (exception instanceof InvalidPassphraseException) { this.wrongPassphrase = true; - Arrays.fill(passphrase, '\0'); + passphrase.destroy(); this.passphrase = null; return true; // reattempting key load } else { @@ -113,13 +112,15 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { if (unlockedSuccessfully && savePassphrase) { savePasswordToSystemkeychain(passphrase); } - Arrays.fill(passphrase, '\0'); + if (passphrase != null) { + passphrase.destroy(); + } } - private void savePasswordToSystemkeychain(char[] passphrase) { + private void savePasswordToSystemkeychain(Passphrase passphrase) { if (keychain.isSupported()) { try { - keychain.storePassphrase(vault.getId(), vault.getDisplayName(), CharBuffer.wrap(passphrase)); + keychain.storePassphrase(vault.getId(), vault.getDisplayName(), passphrase); } catch (KeychainAccessException e) { LOG.error("Failed to store passphrase in system keychain.", e); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java index 637ffd5c6..5e072efd0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryComponent.java @@ -3,15 +3,11 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import dagger.BindsInstance; import dagger.Subcomponent; import org.cryptomator.common.Nullable; -import org.cryptomator.ui.common.Animations; +import org.cryptomator.common.Passphrase; import javax.inject.Named; import javafx.scene.Scene; -import javafx.stage.Stage; -import javafx.stage.Window; -import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; @PassphraseEntryScoped @Subcomponent(modules = {PassphraseEntryModule.class}) @@ -27,7 +23,7 @@ public interface PassphraseEntryComponent { interface Builder { @BindsInstance - PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") char[] savedPassword); + PassphraseEntryComponent.Builder savedPassword(@Nullable @Named("savedPassword") Passphrase savedPassword); PassphraseEntryComponent build(); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java index d048cb776..35b1b1903 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryController.java @@ -4,6 +4,7 @@ import org.cryptomator.common.Nullable; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; +import org.cryptomator.common.Passphrase; import org.cryptomator.ui.common.WeakBindings; import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; @@ -44,7 +45,7 @@ public class PassphraseEntryController implements FxController { private final Stage window; private final Vault vault; private final CompletableFuture result; - private final char[] savedPassword; + private final Passphrase savedPassword; private final ForgetPasswordComponent.Builder forgetPassword; private final KeychainManager keychain; private final StringBinding vaultName; @@ -63,7 +64,7 @@ public class PassphraseEntryController implements FxController { public Animation unlockAnimation; @Inject - public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, @Nullable @Named("savedPassword") char[] savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { + public PassphraseEntryController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture result, @Nullable @Named("savedPassword") Passphrase savedPassword, ForgetPasswordComponent.Builder forgetPassword, KeychainManager keychain) { this.window = window; this.vault = vault; this.result = result; @@ -137,10 +138,7 @@ public class PassphraseEntryController implements FxController { LOG.trace("UnlockController.unlock()"); unlockInProgress.set(true); CharSequence pwFieldContents = passwordField.getCharacters(); - char[] pw = new char[pwFieldContents.length()]; - for (int i = 0; i < pwFieldContents.length(); i++) { - pw[i] = pwFieldContents.charAt(i); - } + Passphrase pw = Passphrase.copyOf(pwFieldContents); result.complete(new PassphraseEntryResult(pw, savePasswordCheckbox.isSelected())); startUnlockAnimation(); } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java index 2c55c203e..f26184d9d 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java @@ -1,6 +1,8 @@ package org.cryptomator.ui.keyloading.masterkeyfile; +import org.cryptomator.common.Passphrase; + // TODO needs to be public due to Dagger -.- -public record PassphraseEntryResult(char[] passphrase, boolean savePassphrase) { +public record PassphraseEntryResult(Passphrase passphrase, boolean savePassphrase) { } diff --git a/src/test/java/org/cryptomator/common/PassphraseTest.java b/src/test/java/org/cryptomator/common/PassphraseTest.java new file mode 100644 index 000000000..7bd71beb2 --- /dev/null +++ b/src/test/java/org/cryptomator/common/PassphraseTest.java @@ -0,0 +1,130 @@ +package org.cryptomator.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +class PassphraseTest { + + @ParameterizedTest + @CsvSource(value = { + "-1, 0", + "0, -1", + "0, 10", + "10, 0", + "10, 10" + }) + public void testInvalidConstructorArgs(int offset, int length) { + char[] data = "test".toCharArray(); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> { + new Passphrase(data, offset, length); + }); + } + + @ParameterizedTest + @CsvSource(value = { + "0, 4", + "0, 0", + "0, 1", + "1, 1", + "2, 2" + }) + public void testValidConstructorArgs(int offset, int length) { + char[] data = "test".toCharArray(); + var pw = new Passphrase(data, offset, length); + Assertions.assertEquals(length, pw.length()); + Assertions.assertEquals("test".substring(offset, offset + length), pw.toString()); + } + + @Test + public void testToString() { + Assertions.assertEquals("test", Passphrase.copyOf("test").toString()); + } + + @Nested + class WithInstances { + + private Passphrase pw1; + private Passphrase pw2; + + @BeforeEach + public void setup() { + char[] foo = "test test".toCharArray(); + pw1 = new Passphrase(foo, 5, 4); + pw2 = Passphrase.copyOf("test"); + } + + @Test + public void testEquals() { + Assertions.assertEquals(pw1, pw2); + Assertions.assertEquals("test", pw1.toString()); + Assertions.assertEquals("test", pw2.toString()); + } + + @Test + public void testHashcode() { + Assertions.assertEquals(pw1.hashCode(), pw2.hashCode()); + } + + @Test + public void testLength() { + Assertions.assertEquals(4, pw1.length()); + Assertions.assertEquals(4, pw2.length()); + } + + @Test + public void testCharAt() { + Assertions.assertEquals('s', pw1.charAt(2)); + } + + @ParameterizedTest + @ValueSource(ints = {-1, 4, 5}) + public void testInvalidCharAt(int idx) { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> pw1.charAt(idx)); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3}) + public void testValidCharAt(int idx) { + Assertions.assertEquals("test".charAt(idx), pw1.charAt(idx)); + } + + @ParameterizedTest + @CsvSource(value = { + "-1, 0", + "0, -1", + "-1, -1", + "0, 5", + "3, 2" + }) + public void testInvalidSubSequence(int start, int end) { + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> pw1.subSequence(start, end)); + } + + @ParameterizedTest + @CsvSource(value = { + "0, 4", + "1, 4", + "0, 2", + "2, 4", + "4, 4", + }) + public void testValidSubSequence(int start, int end) { + Assertions.assertEquals("test".substring(start, end), pw1.subSequence(start, end).toString()); + } + + @Test + public void testDestroy() { + pw2.destroy(); + Assertions.assertFalse(pw1.isDestroyed()); + Assertions.assertTrue(pw2.isDestroyed()); + Assertions.assertNotEquals(pw1, pw2); + } + + } + +} \ No newline at end of file From e95853deacab30afe0420449c05e0f338a561347 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 16:46:57 +0100 Subject: [PATCH 010/109] make test public, fixing InaccessibleObject error in maven build --- .../org/cryptomator/common/PassphraseTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/test/java/org/cryptomator/common/PassphraseTest.java b/src/test/java/org/cryptomator/common/PassphraseTest.java index 7bd71beb2..02f640e94 100644 --- a/src/test/java/org/cryptomator/common/PassphraseTest.java +++ b/src/test/java/org/cryptomator/common/PassphraseTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; -class PassphraseTest { +public class PassphraseTest { @ParameterizedTest @CsvSource(value = { @@ -40,13 +40,8 @@ class PassphraseTest { Assertions.assertEquals("test".substring(offset, offset + length), pw.toString()); } - @Test - public void testToString() { - Assertions.assertEquals("test", Passphrase.copyOf("test").toString()); - } - @Nested - class WithInstances { + public class InstanceMethods { private Passphrase pw1; private Passphrase pw2; @@ -59,12 +54,16 @@ class PassphraseTest { } @Test - public void testEquals() { - Assertions.assertEquals(pw1, pw2); + public void testToString() { Assertions.assertEquals("test", pw1.toString()); Assertions.assertEquals("test", pw2.toString()); } + @Test + public void testEquals() { + Assertions.assertEquals(pw1, pw2); + } + @Test public void testHashcode() { Assertions.assertEquals(pw1.hashCode(), pw2.hashCode()); From 983a4d0b0faa554664127b41c3b52b67684849dc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 17:00:49 +0100 Subject: [PATCH 011/109] move masterkey selection to subcomponent and use CompletableFuture instead of UserInteractionLock + AtomicReference --- .../ChooseMasterkeyFileComponent.java | 25 +++++++++++ .../ChooseMasterkeyFileModule.java | 42 ++++++++++++++++++ .../ChooseMasterkeyFileScoped.java | 13 ++++++ .../MasterkeyFileLoadingModule.java | 40 +---------------- .../MasterkeyFileLoadingStrategy.java | 44 +++++++------------ .../SelectMasterkeyFileController.java | 24 +++------- 6 files changed, 103 insertions(+), 85 deletions(-) create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java create mode 100644 src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java new file mode 100644 index 000000000..a548cd47d --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileComponent.java @@ -0,0 +1,25 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Subcomponent; + +import javafx.scene.Scene; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +@ChooseMasterkeyFileScoped +@Subcomponent(modules = {ChooseMasterkeyFileModule.class}) +public interface ChooseMasterkeyFileComponent { + + @ChooseMasterkeyFileScoped + Scene chooseMasterkeyScene(); + + @ChooseMasterkeyFileScoped + CompletableFuture result(); + + @Subcomponent.Builder + interface Builder { + + ChooseMasterkeyFileComponent build(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java new file mode 100644 index 000000000..1cc71cbfa --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -0,0 +1,42 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import dagger.Module; +import dagger.Provides; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; + +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; + +@Module +abstract class ChooseMasterkeyFileModule { + + @Provides + @ChooseMasterkeyFileScoped + static CompletableFuture provideResult() { + return new CompletableFuture<>(); + } + + @Provides + @ChooseMasterkeyFileScoped + static Scene provideChooseMasterkeyScene(SelectMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + // TODO: simplify FxmlLoaderFactory + try { + var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE.getRessourcePathString()); + var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); + Parent root = loader.load(); + return sceneFactory.apply(root); + } catch (IOException e) { + throw new UncheckedIOException("Failed to load UnlockScene", e); + } + } + + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java new file mode 100644 index 000000000..4bf8c5c24 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.keyloading.masterkeyfile; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +@interface ChooseMasterkeyFileScoped { + +} diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 7f55de898..af592cb3b 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -8,12 +8,6 @@ import dagger.multibindings.StringKey; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.integrations.keychain.KeychainAccessException; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxControllerKey; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; @@ -22,27 +16,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Named; -import javafx.scene.Scene; -import java.nio.file.Path; import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; -@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class}) +@Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class}) public abstract class MasterkeyFileLoadingModule { private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class); - public enum MasterkeyFileProvision { - MASTERKEYFILE_PROVIDED, - CANCELED - } - - @Provides - @KeyLoadingScoped - static UserInteractionLock provideMasterkeyFileProvisionLock() { - return new UserInteractionLock<>(null); - } - @Provides @Named("savedPassword") @KeyLoadingScoped @@ -59,24 +39,6 @@ public abstract class MasterkeyFileLoadingModule { } } - @Provides - @KeyLoadingScoped - static AtomicReference provideUserProvidedMasterkeyPath() { - return new AtomicReference<>(); - } - - @Provides - @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) - @KeyLoadingScoped - static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); - } - - @Binds - @IntoMap - @FxControllerKey(SelectMasterkeyFileController.class) - abstract FxController bindUnlockSelectMasterkeyFileController(SelectMasterkeyFileController controller); - @Binds @IntoMap @KeyLoadingScoped diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index a22903973..13a7dadda 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -1,7 +1,7 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import com.google.common.base.Preconditions; -import dagger.Lazy; +import org.cryptomator.common.Passphrase; import org.cryptomator.common.keychain.KeychainManager; import org.cryptomator.common.vaults.Vault; import org.cryptomator.cryptofs.common.BackupHelper; @@ -11,10 +11,6 @@ import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.cryptomator.integrations.keychain.KeychainAccessException; import org.cryptomator.ui.common.Animations; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.common.Passphrase; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; import org.cryptomator.ui.unlock.UnlockCancelledException; @@ -22,7 +18,6 @@ import org.cryptomator.ui.unlock.UnlockCancelledException; import javax.inject.Inject; import javax.inject.Named; import javafx.application.Platform; -import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.Window; import java.io.IOException; @@ -32,7 +27,6 @@ import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicReference; @KeyLoading public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { @@ -42,10 +36,8 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final Vault vault; private final MasterkeyFileAccess masterkeyFileAccess; private final Stage window; - private final Lazy selectMasterkeyFileScene; private final PassphraseEntryComponent.Builder passphraseEntry; - private final UserInteractionLock masterkeyFileProvisionLock; - private final AtomicReference filePath; + private final ChooseMasterkeyFileComponent.Builder masterkeyChooser; private final KeychainManager keychain; private Passphrase passphrase; @@ -53,14 +45,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private boolean wrongPassphrase; @Inject - public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) Lazy selectMasterkeyFileScene, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, UserInteractionLock masterkeyFileProvisionLock, AtomicReference filePath, KeychainManager keychain) { + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyChooser, KeychainManager keychain) { this.vault = vault; this.masterkeyFileAccess = masterkeyFileAccess; this.window = window; - this.selectMasterkeyFileScene = selectMasterkeyFileScene; this.passphraseEntry = passphraseEntry; - this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; - this.filePath = filePath; + this.masterkeyChooser = masterkeyChooser; this.keychain = keychain; this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null); this.savePassphrase = savedPassphrase.isPresent(); @@ -72,7 +62,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { try { Path filePath = vault.getPath().resolve(keyId.getSchemeSpecificPart()); if (!Files.exists(filePath)) { - filePath = getAlternateMasterkeyFilePath(); + filePath = askUserForMasterkeyFilePath(); } if (passphrase == null) { askForPassphrase(); @@ -127,20 +117,10 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { } } - private Path getAlternateMasterkeyFilePath() throws UnlockCancelledException, InterruptedException { - if (filePath.get() == null) { - return switch (askUserForMasterkeyFilePath()) { - case MASTERKEYFILE_PROVIDED -> filePath.get(); - case CANCELED -> throw new UnlockCancelledException("Choosing masterkey file cancelled."); - }; - } else { - return filePath.get(); - } - } - - private MasterkeyFileLoadingModule.MasterkeyFileProvision askUserForMasterkeyFilePath() throws InterruptedException { + private Path askUserForMasterkeyFilePath() throws InterruptedException { + var comp = masterkeyChooser.build(); Platform.runLater(() -> { - window.setScene(selectMasterkeyFileScene.get()); + window.setScene(comp.chooseMasterkeyScene()); window.show(); Window owner = window.getOwner(); if (owner != null) { @@ -150,7 +130,13 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { window.centerOnScreen(); } }); - return masterkeyFileProvisionLock.awaitInteraction(); + try { + return comp.result().get(); + } catch (CancellationException e) { + throw new UnlockCancelledException("Choosing masterkey file cancelled."); + } catch (ExecutionException e) { + throw new MasterkeyLoadingFailedException("Failed to select masterkey file.", e); + } } private void askForPassphrase() throws InterruptedException { diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java index 39be2b36e..fc225e0a8 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java @@ -1,10 +1,7 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; import org.cryptomator.ui.keyloading.KeyLoading; -import org.cryptomator.ui.keyloading.KeyLoadingScoped; -import org.cryptomator.ui.keyloading.masterkeyfile.MasterkeyFileLoadingModule.MasterkeyFileProvision; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,23 +13,21 @@ import javafx.stage.WindowEvent; import java.io.File; import java.nio.file.Path; import java.util.ResourceBundle; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.CompletableFuture; -@KeyLoadingScoped +@ChooseMasterkeyFileScoped public class SelectMasterkeyFileController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class); private final Stage window; - private final AtomicReference masterkeyPath; - private final UserInteractionLock masterkeyFileProvisionLock; + private final CompletableFuture result; private final ResourceBundle resourceBundle; @Inject - public SelectMasterkeyFileController(@KeyLoading Stage window, AtomicReference masterkeyPath, UserInteractionLock masterkeyFileProvisionLock, ResourceBundle resourceBundle) { + public SelectMasterkeyFileController(@KeyLoading Stage window, CompletableFuture result, ResourceBundle resourceBundle) { this.window = window; - this.masterkeyPath = masterkeyPath; - this.masterkeyFileProvisionLock = masterkeyFileProvisionLock; + this.result = result; this.resourceBundle = resourceBundle; this.window.setOnHiding(this::windowClosed); } @@ -43,11 +38,7 @@ public class SelectMasterkeyFileController implements FxController { } private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, mark this workflow as cancelled: - if (masterkeyFileProvisionLock.awaitingInteraction().get()) { - LOG.debug("Unlock canceled by user."); - masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.CANCELED); - } + result.cancel(true); } @FXML @@ -59,8 +50,7 @@ public class SelectMasterkeyFileController implements FxController { File masterkeyFile = fileChooser.showOpenDialog(window); if (masterkeyFile != null) { LOG.debug("Chose masterkey file: {}", masterkeyFile); - masterkeyPath.set(masterkeyFile.toPath()); - masterkeyFileProvisionLock.interacted(MasterkeyFileProvision.MASTERKEYFILE_PROVIDED); + result.complete(masterkeyFile.toPath()); } } From 6ca87d13f57e281ca613f283bcbe75d4b3498ee1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 16 Dec 2021 17:01:56 +0100 Subject: [PATCH 012/109] renamed class --- ...Controller.java => ChooseMasterkeyFileController.java} | 6 +++--- .../masterkeyfile/ChooseMasterkeyFileModule.java | 2 +- .../masterkeyfile/MasterkeyFileLoadingStrategy.java | 8 ++++---- src/main/resources/fxml/unlock_select_masterkeyfile.fxml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/{SelectMasterkeyFileController.java => ChooseMasterkeyFileController.java} (86%) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java similarity index 86% rename from src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java rename to src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java index fc225e0a8..11cf7bd6b 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/SelectMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java @@ -16,16 +16,16 @@ import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; @ChooseMasterkeyFileScoped -public class SelectMasterkeyFileController implements FxController { +public class ChooseMasterkeyFileController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(SelectMasterkeyFileController.class); + private static final Logger LOG = LoggerFactory.getLogger(ChooseMasterkeyFileController.class); private final Stage window; private final CompletableFuture result; private final ResourceBundle resourceBundle; @Inject - public SelectMasterkeyFileController(@KeyLoading Stage window, CompletableFuture result, ResourceBundle resourceBundle) { + public ChooseMasterkeyFileController(@KeyLoading Stage window, CompletableFuture result, ResourceBundle resourceBundle) { this.window = window; this.result = result; this.resourceBundle = resourceBundle; diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java index 1cc71cbfa..27f1247e4 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -26,7 +26,7 @@ abstract class ChooseMasterkeyFileModule { @Provides @ChooseMasterkeyFileScoped - static Scene provideChooseMasterkeyScene(SelectMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { // TODO: simplify FxmlLoaderFactory try { var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE.getRessourcePathString()); diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index 13a7dadda..a1d85cb36 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -37,7 +37,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final MasterkeyFileAccess masterkeyFileAccess; private final Stage window; private final PassphraseEntryComponent.Builder passphraseEntry; - private final ChooseMasterkeyFileComponent.Builder masterkeyChooser; + private final ChooseMasterkeyFileComponent.Builder masterkeyFileChoice; private final KeychainManager keychain; private Passphrase passphrase; @@ -45,12 +45,12 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private boolean wrongPassphrase; @Inject - public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyChooser, KeychainManager keychain) { + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain) { this.vault = vault; this.masterkeyFileAccess = masterkeyFileAccess; this.window = window; this.passphraseEntry = passphraseEntry; - this.masterkeyChooser = masterkeyChooser; + this.masterkeyFileChoice = masterkeyFileChoice; this.keychain = keychain; this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null); this.savePassphrase = savedPassphrase.isPresent(); @@ -118,7 +118,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { } private Path askUserForMasterkeyFilePath() throws InterruptedException { - var comp = masterkeyChooser.build(); + var comp = masterkeyFileChoice.build(); Platform.runLater(() -> { window.setScene(comp.chooseMasterkeyScene()); window.show(); diff --git a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml index b6539f88f..43f2f3e46 100644 --- a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml +++ b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml @@ -11,7 +11,7 @@ Date: Thu, 16 Dec 2021 17:09:25 +0100 Subject: [PATCH 013/109] convert Dagger modules to interfaces --- .../masterkeyfile/ChooseMasterkeyFileModule.java | 2 +- .../masterkeyfile/MasterkeyFileLoadingModule.java | 7 ++----- .../ui/keyloading/masterkeyfile/PassphraseEntryModule.java | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java index 27f1247e4..bd9ab42c0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -16,7 +16,7 @@ import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; @Module -abstract class ChooseMasterkeyFileModule { +interface ChooseMasterkeyFileModule { @Provides @ChooseMasterkeyFileScoped diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index af592cb3b..9375b0cff 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -12,16 +12,13 @@ import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent; import org.cryptomator.ui.keyloading.KeyLoading; import org.cryptomator.ui.keyloading.KeyLoadingScoped; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; -import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Named; import java.util.Optional; @Module(subcomponents = {ForgetPasswordComponent.class, PassphraseEntryComponent.class, ChooseMasterkeyFileComponent.class}) -public abstract class MasterkeyFileLoadingModule { - - private static final Logger LOG = LoggerFactory.getLogger(MasterkeyFileLoadingModule.class); +public interface MasterkeyFileLoadingModule { @Provides @Named("savedPassword") @@ -33,7 +30,7 @@ public abstract class MasterkeyFileLoadingModule { try { return Optional.ofNullable(keychain.loadPassphrase(vault.getId())); } catch (KeychainAccessException e) { - LOG.error("Failed to load entry from system keychain.", e); + LoggerFactory.getLogger(MasterkeyFileLoadingModule.class).error("Failed to load entry from system keychain.", e); return Optional.empty(); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java index aec32e5e6..0d67abd50 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -15,7 +15,7 @@ import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; @Module -abstract class PassphraseEntryModule { +interface PassphraseEntryModule { @Provides @PassphraseEntryScoped From 2afd9f1c1d399b7c434819e7a4a3d2bd92cf167b Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 17 Dec 2021 14:52:18 +0100 Subject: [PATCH 014/109] Bump logback hotfix 1.2.8 is superseded by 1.2.9 as stated in https://jira.qos.ch/browse/LOGBACK-1591?focusedCommentId=20920&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-20920 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9fb10faef..6f0243597 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 2.8.9 1.5.2 1.7.32 - 1.2.8 + 1.2.9 5.8.1 From 550903325d253ba470ece774e8f7d649c9d3366b Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 4 Jan 2022 16:35:09 +0100 Subject: [PATCH 015/109] Add vault name in unlock window titles --- .../MasterkeyFileLoadingModule.java | 22 +++++++++++++++---- src/main/resources/i18n/strings.properties | 3 ++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java index 901eacfb9..72e902ded 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingModule.java @@ -23,8 +23,10 @@ import org.slf4j.LoggerFactory; import javax.inject.Named; import javafx.scene.Scene; +import javafx.stage.Stage; import java.nio.file.Path; import java.util.Optional; +import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -93,15 +95,27 @@ public abstract class MasterkeyFileLoadingModule { @Provides @FxmlScene(FxmlFile.UNLOCK_ENTER_PASSWORD) @KeyLoadingScoped - static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); + static Scene provideUnlockScene(@KeyLoading FxmlLoaderFactory fxmlLoaders, @KeyLoading Stage window, @KeyLoading Vault v, ResourceBundle resourceBundle) { + var scene = fxmlLoaders.createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); + scene.windowProperty().addListener((prop, oldVal, newVal) -> { + if (window.equals(newVal)) { + window.setTitle(String.format(resourceBundle.getString("unlock.title"), v.getDisplayName())); + } + }); + return scene; } @Provides @FxmlScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE) @KeyLoadingScoped - static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); + static Scene provideUnlockSelectMasterkeyFileScene(@KeyLoading FxmlLoaderFactory fxmlLoaders, @KeyLoading Stage window, @KeyLoading Vault v, ResourceBundle resourceBundle) { + var scene = fxmlLoaders.createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); + scene.windowProperty().addListener((prop, oldVal, newVal) -> { + if (window.equals(newVal)) { + window.setTitle(String.format(resourceBundle.getString("unlock.chooseMasterkey.title"), v.getDisplayName())); + } + }); + return scene; } @Binds diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 7b17fa791..63e2bbecf 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -103,7 +103,8 @@ unlock.title=Unlock "%s" unlock.passwordPrompt=Enter password for "%s": unlock.savePassword=Remember Password unlock.unlockBtn=Unlock -## +## Select +unlock.chooseMasterkey.title=Select Masterkey of "%s" unlock.chooseMasterkey.prompt=Could not find the masterkey file for this vault at its expected location. Please choose the key file manually. unlock.chooseMasterkey.filePickerTitle=Select Masterkey File ## Success From 7486fa2167418734a28647c1044a906ba2c1bb81 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 4 Jan 2022 16:35:42 +0100 Subject: [PATCH 016/109] update Third parties license file --- src/main/resources/license/THIRD-PARTY.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/license/THIRD-PARTY.txt b/src/main/resources/license/THIRD-PARTY.txt index 32680971b..ba4310dd5 100644 --- a/src/main/resources/license/THIRD-PARTY.txt +++ b/src/main/resources/license/THIRD-PARTY.txt @@ -52,13 +52,13 @@ Cryptomator uses 40 third-party dependencies under the following licenses: - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet) - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util) Eclipse Public License - v 1.0: - - Logback Classic Module (ch.qos.logback:logback-classic:1.2.8 - http://logback.qos.ch/logback-classic) - - Logback Core Module (ch.qos.logback:logback-core:1.2.8 - http://logback.qos.ch/logback-core) + - Logback Classic Module (ch.qos.logback:logback-classic:1.2.9 - http://logback.qos.ch/logback-classic) + - Logback Core Module (ch.qos.logback:logback-core:1.2.9 - http://logback.qos.ch/logback-core) Eclipse Public License - v 2.0: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) GNU Lesser General Public License: - - Logback Classic Module (ch.qos.logback:logback-classic:1.2.8 - http://logback.qos.ch/logback-classic) - - Logback Core Module (ch.qos.logback:logback-core:1.2.8 - http://logback.qos.ch/logback-core) + - Logback Classic Module (ch.qos.logback:logback-classic:1.2.9 - http://logback.qos.ch/logback-classic) + - Logback Core Module (ch.qos.logback:logback-core:1.2.9 - http://logback.qos.ch/logback-core) GPLv2: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) GPLv2+CE: From bf382d928f3c18c9e967163ce41fed5f12d05e8e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 5 Jan 2022 10:51:15 +0100 Subject: [PATCH 017/109] Update dokan.dll [ci skip] --- dist/win/contrib/dokan1.dll | Bin 524800 -> 520088 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/win/contrib/dokan1.dll b/dist/win/contrib/dokan1.dll index badc12b2ad424a7045097df3a5ed25df72b1f135..194945aed8d0aadb36aa6fc8999e71a8080eded4 100755 GIT binary patch delta 127028 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 N#`~%)J$Xin{{`5*n(_bu delta 127547 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

b^+DxeKuF2Vw!10W7G9|3n7{wsi; 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%0D5u-3P)vl$)M2TNXQ)?=hqr~af zhlS{~cC4fJLUC*rFr35V)CD($b>%qddWO+<*qLniNw!zTtl|XTeZ}O?))m9G>mKso z`}ieOZ#NtwpWU#NJKmqbfj#^Yj>WF~2@X!x_F}@P`yBbs?&4LgEbpUyVW7KIsNAh5 z)-{Q+Vp^Qp-UXW{yu&%YhZBpVCREN3!&P5|VBGPy->#Qn_W0x|((o?{(rdM%PDUl8 zzL;Hodwqx{FG-Un1|IM8*OdG9#r9JD>)T=*h&oZKUAQfwq1Z$8bB_Y4p3bC^gTGnU zV-PC2qI}gzd`D`1RjJlkOse(*3rX|dUvUa`s@M~4Uh}Flw6WN;`bE6vQE%cDm(tSv z(Y2SA?S*K6!wd4t zzM@=eBF1~ppr<@@6O@|K;sR;GLFKz>F(_=pC3j(tyO-AW17zK*2*`WLzGo-X+;UOE z+WAUxwAd}YG0-uW8|cWzN5IXtp%F>Q>mmN!5T+((wcTK4U{iGS&6kvKnu;5PLu-2o zdyAw>-v02MDtdP6>0T^l@kB4vsLuCQ-fJed6+0_Cn~BNNNA;G(h;;*v!{~Y|dPYuA z$8ZlJYb50{jz;-4XO#9a;t;9B8D(vZ*u?AWnwbB3uLx7F#fS%j)w&QHEkKDqPC87v z8$|>&@Yku-qji;iv10AuHDxq*(@HIJ?M7anbXBgaxHZR!)FD)fBezFQWps0~o}oT+ znLcf{_<<+R&Su+97i8}}HYwOr@_};en`62jz52u&#VU zX`3hpDgJR{lJwJY3K*0zaiXs@{J7GzCj!gj#L%#x12KchswV3W!-L@}8O@;E4L=EC zpt`?;aw$%nC^ZRGI>(ECe(T$jj85`tsoEQ-d&QCJC+(DL@nTf#FPoK%3q+i(J6Oxb zXs_z^>|}OcrmwoGS2?Pu_$(1Ptmwr#NT+I(8}2UaI@eN~v=ZN~K7Sx?uwp+&FeV(! z8jdE6IjZbxCAO444paVUB_>Mp03{|t{6Ly`q^yQs%E<)S`hG{0hY1i~*CR@7YjK(M zNgd@xYq6>Het;q+iV4!>!;~UR^5SlgawZX$>0N(C*G6n8eR4>NX+z;d%CI(A>zV-(Nu|w`b#)9% zMv@p&Jpeehb_28l34yrEgoi;6F+njg>rMjU&GNeJ#`g25bX94if(}G0VU!Gu;bnd2;1T-;06&GN2~jP z2S@Tz_6mK=H$E{MBv}>hBbBr5#M;%L9MX(rmqKKS$8x%xhm?x#p#?uyQ`)tM^318G zWVRO@MfItMT{zt^{O(oJoNGl|+w0yv;EulEdojc+@T#C|SCW&w z?CrgZW`F{hdNAreuZkkeCTv`|!5|lpB&?%HA!4o}M%ESKw|o{`_VFxereE9;SzO=G z!xbqUh{Ue!k$wenCRPrE?g3K4`W5Pb5~% zNTEwcdtrzyT1X{Aq{h;hl{u|Mvo6h? zgsN}zit8j~GCeLStCd#g$38rm>ApcU6z}9U29J%?Q0R+@QdOm%Nwn0M1-}W0)EH6Z zhskkq`fU2lle(d<@{vhwEEVolcALb2w>(go;vx7FoY8q49bf?TpgK|_L1s6;S}#;< zW9NBeA6zur0DHP2**sq2>$Br6%R7^Cv>n4#HqY#ySKj+>BG0E@$JTK3~-l=EK%$zxM=FFKHRySRJ zKab$#DrHwdKG+EX5d~1Ld>S|U>eWN))Q4#Fu`1}b&W3MRMP840KgyC4>N#>dHyTtO zWc?n%l%aG%_D2&Y+4$_PtW}dO)H@1=BDE@|P~C zD!<>AMWpwIk-E~oXrS&W#Ti>laW-x1!zz^#OrkVuMU~pt0Rg9b(MU~u0!@p2q=yH= z^~8Q6QRlz&y`vzmj1FtU4U8O~X#mkq^2U9Yevq0&Qw(_U(fliv#M*8;l0Kf`eidm? zbX?{$Df2Q0A~cKnsBj!JkG|nyJqc;C4$_%RdQ6)wWuC<_G8c80=FU+Z2~!7~i@Hut zG8ZLIjWri_pBiBon%IYuJ{jbAsHZ|lYa(if2aiKq{E<4_l6-cd(~HP2xKen@!|Oa$$B zu`>7y?nFA3a03uL9LnP{Hs28h^O=G};?zdedI8jW^Hw7WFWRUD_oYPFRd+{npHSUV zhtuKNrzCYZ=Bq;pA(Jbmp>R|Rj^0ux*4J?KG0sGT8ONB5QjC4fMFWiSQ`=;`7&NVg z!G*)yy2gYeNlr!8fw|R-UN^BM=UY;6+sbJv%SAP}NR|O!4l*G_57nUZogLtMfnuF4 z$`Jm6yisM|gX9g=@2va(JA=|reo>0JQwX@p za@JW>)Tx9b_XxVXOT&97vs&qzA0eY9%a~CL3>HsIPcO$As%+L}(`f4r^G^aMs!n*- z1e>|OVZd4G>F;kS2l(SLAk}(lt5Vc|4#+R)Zt2=Eb*Wj_CY!!No?EL8ZW>fh6jy60r8n zcl=-?^J}L8Iv6LNjg4$w4s0wo`Zm8!!^V`F;Y`SQ;Wh2;XUCYSKWO(X8XnoHe#*>T z`5;eBW+DB@+;a0>y4fP7P`<2iKOheRwNCd~N30x_2lG>ACyaY{a+g!WXD`bN_y z>Lym92Js>#uytnghIe^LPpn?9RN%vVvOu5R6~IIPt|864f(Er}_f+5uda{+md-r){ zFBZzq@x)%xdhhv;Pw2(E2x(frs~6L=ru{Y@e4GHJ|`5- zx0ghSaq3hA(;E2*SS*5-+d1NBA{%&{pX|-L3Nack^_DJgx=#*`>^^ors3W+hZ(rydZQI$1&_+wB_iM6hxxNJ{474ci$Mp-xZH6@ zVS{*mS6+RW7b9=b(@Vq%M@5X=ucz^C)w%?`^=Cmf0!x7^8A)pX=mwwEfz@aOd{Y?7 zRiVa6LSwr8djsN8Wv#wkPR1am5j_yR40t+6V&wVkigq0p4*?*;2-v9K0?ec zHCgDtl4UE&>IpyYq@{Nqq1rWGWdI8oR;zw?n)e&Py0@GI8Owb-B$^NyD05s`t&qFY zXCz*4M~HgK-*O`IsaN^;1F+n$=in~~u;B0t{{j(+3<^mQ#5g(S8t*WW^$5Ubv> z=sX7CTqH*niBN!Ccbmc<3y3|wUYA_tC4*R9QHOHs4Q9c9KkNX4n9!8jD7iq9k#SG` zZ+ysL);j9Q3m713zs9e$zd$#oskFaPhy6+T70EwG{sO~U+TS?yg_a*0%xd|+ji_v| z9Ua@Ak+K@o+^>w|1VsNnnE4qRBHCF=4Ge~xmPLO!Hvq-88L<(jiaVG*JQ`C-80N5& zzqNF67~a|3h>6Tu6>d@+VX?5c6Yudhdn^45qoY8QN?v@Gv1g!%dABUz68XFgF(N`VudtR4Ifj~&9oeU7Ms`6zZR5#Aj> zVF*hUXBBJt-XYj08d3~{Pv0@{-WkGzt33qWIA$vRF$L+*%HMO{P*%H2vC@&EOBaEZ zF+*8GB-%*zARxLOXXGUpo|%rwPH3S6kaU zT{5H+y7}_avxKhiyFVj?B99ix&eIsEWQ~jFp_$+!rAWwy_+@_76qyQT8)X9bi9>5h z&IwBM*sxrQk2U2PJAlVlq4KRMxmX++YokOww1;fXx1Paxz&q(EcLO=ULsUhIa+q9H zNOHdO3$*>k=R`Wdd(T!rVHj)Oz=nW~*x{xY8PnNtQxj~S2q24T2$c?bn;+>_G8+3_ z%g+sCz5S{qf`mA0alCX$NWj*8I{@qOEpIuTg^Zk@M^X=XKQP3(ODy*=;GBb#EXld? zmgGErM$rJ%NUQe5GcD8sszS+xrP)#F#MdEAiC2)8htUK=m3c_SMnV|T$q#(@a8_R& z^-Rm}4QJtE6F9UZSgW9kAQs0wlv^gybw`L3(~0(f-|&GWSX6rAsnVv0K$j=)IY;eG zi?W0qT9qa2bv8j~{Q}9u39(R>f_?MJOS17{I7(L@pv3s&O70xw&b21&g(lW<05nx@ z&ugVZ(lCS|!vgGXr(=7&+&VAxFY3jcXh{<3lW)X&sRJzAqkXir;5=&5zFAs__AzM< z+qW=wZ{N~by-}<7SOM)+hK%o~uYp(HGKqXCc_GoeS4OkQOIVMG!!3o@qQ+cwjL_D8 z@l+uKsHmQ5`PPvTF_OOK=SD&VDLFwC30g&&a0}$|XZf>{EV#Zu)H)|Sdx1166JTvM z%;=! zHZ_KnFFr95c4$)^OGHk@@5viQDK z7Fek-mZv~_$P>_WDr-_LiZHS1VU7}x4B9}1hm2xvDtIAWuFi*zV!`YIpFN5-i?|ET zpL+rxK~r8Y`8y0gm_E^z3W+>V&dB4JM?om6eveC|neW>*{=hWzcU){UQ#JJ9GmwVz zJeKI#3Y@Z8qv-Y(r-e#~7&^)zK}tCJ>?TMnAlFG;|Mg2fKMiNcc*q#m zxc<|}ba?OY_}B)f@FZKyt6#yQ*D~=jrl4QYala@c#C7>+K4lDRAbjzduNedTC(C2L zZwwnCg(9_-d4YqIVP$E}|B9e#e1` z*hpg%3VAM1BV}D^l4YV&CY4Xhx=kdvD(v5rP?3+Q!|fCZvpxbmp+p=5ebw_IF-; z9Baaoc;Yw~-s8bQ<7Qn=QJp5o@;#DJIv2>-LDu9n+1O19Twa-uZA*H)R$+Wad$-iv&ric%yLcM zp$w#2kwFSer0M09{0>mF>Z_fi4${`qq=*0FS)+4x;#x;@~c_Gk;oY; zkE+H$8qex9HRHyR2oKUJJlCRI7#8GmF8R=uOYiTjOA&*aRss2jfy>Fyqod{{_7GU074b z@Fo+Oe~beXkJ19PXMm9zD!V|>`W#RkhAGL?0Y`}H+zWc}@!`DcMAos=HU}PH?(QXb zJj4e~WCMJjWt07(YJUOptL5N_C$f<6XOM-7>4aj$wEc`Y9XNs%i65v<@heZ>&)Ivh zl-Vcqp!YD=*2p~eJ=W0o17yth(YL*1x|U5`2P|i}%%{D_TBp}TR6@*yWNT59WpoZn z8~^*MU@a*0(;g6+}hJiYY?gQex)zd&+mn`Cf+F)*AWpygg{QHluTl zT}~y)s_IYMdZ{u`Hu2e~P%1E^g=v92`;fvB3NSJuO~@p{S|J5nJV`j{%12FxRU_e^ zY<)_IXX4pTKUKmhl>hJtt7wl{BAa|fV@|mt|Dp)aBq!vF-1abasVUcB zYk7T@#zQfzOvZ7Fn*TM%H|dNBg%-ZDNO_ZRU7jb#Ln{VKsgd@~C`?$4$b# z-(nXZHi?DQ=#J8yO-eVvOq3#nFF}g*pLVK6(J4$(KR?7^ZRv;T_}F@;%GQLuBui^W z@-c{iqAN-sDx3!h7h*+myfG2Fd!*-3n^;=@fE09lXlzEBtC^|!jT&xAnL_8lO zrExH-(9KNLbpl>e8W3B(4{zAcmtN-6^wT1d%Vd0|2N-rTq@rla{GR$qwCI z+|gLiTpX9y+Kh@W^Z~x=+s_0{Hv1|HSLCbWJeiHHM0Lh^z}HTJ!u5CDP>vZxZ|lXJ zhf)7XLWW-4FcnTz{Mw#^tfW=Ah6a}TwO15?NlWQ38fX@^=Ln|9$m;A(#4 zOC5uer4A<=P5X9GM`|zpguamuK_gz%d#CGm0Ss4?|Pn(U^RL z`ERXc#)XeAb)XI-o`|);Yse#1$RiV>}nX!6W#f;(>(!yjKbgn!{ z9vTdU2Ku3uMLLM6=YA>=E2)z{SViPOC{;JCQYl>F==t&#V5p>7|7Mzw<p=qUL6A@9*=aSct(#8Rx(9(TcoJyo1HdL6 zGh#d9#KT%NM`_FLRjRJ-jRdq~8>CW2CwQGc@)cz}1wd&*goLNcw(xE)K^gzKWAMw~ zbb?OHMX>Z6j&FO*mRFynt|PG-DS>ZGXJ11OKHJbw)XA{uiRuS;pW@<9&bGp8pYsye8@~_ z7$1GgGiEYh;l*wqlE!L!nU7*o>3iZX-#wFc_RF|S7yl#|##<7KfLuX5$rAFR8{B&q z1c)AYd81h@&@&pTWY6Du&siwDXz?3m7sD$TY*xzt>kiMF#X8p7Mb~Xqu8cN1rOXlv z@B91?zYU<{&4{Eil+?!4*a*8TIFJCp)(AMD1!ZVKu~$f2z=iS?rBZp05rBiVm{Te3 zz|yqVv#*n$LQ+TP*X0G=;ln>0H|UfV0U*vilboH(Vx1XZ*)WFc+t+)z}G z*tfxo39#Hd&z%WYSK?hZ?X*Sw-iItesJ)fH{16Q~_y-<3o7JxSRyla2nCh}ffk!Ah z#wg5G8@`ASpUnd6zPgR}NP?otv0SMD2142em+N^~FON7(ZhGXNxtOn=%|fcBY*82` z&`|}QBRlw|*{qfjx0pYg&4TrD-EPCH{EIYbiMws)=h9fvz{kYP+tAa@Ze74#3Q$<_1-Zu~U`%Jd;;pkm z9ojzb;c6KwCuF(OIw;np5O6-Yh3d^NL`JOH{SffZBCbzo!PQr6#AOU^f{l0~@ETE< zJZ=a7Af44q$R{bjWvTe{H{B4Sb?5g+{?q6h*#3mNZGW(4CGJrmkNld*OjTOE+RpE! zvynpEn>;Rq)eEZzAlNQ3|L)eH3GkasL}}hz=tiqnyvgTfu;9AKut?aik`d;f2D{v~ z#kef9WmufU&t$;V%VFhzWU!jTc`L6thlNPdIq0(M;m?hY(H37@d20j;LJl7|hefpb z^(bl-U0S1>H&COn-_;f&>&u6jMNy*$sF7Nk9C_}SJZBC|3{Afdy0pTUCJxIAWj39N z5P8HV0MyEsTikyxi)p$5^L&z}nLhhjyk#R06>4Qt^_$yDJ({n}!Ue z70<%GG>}oW@4@e&YheH-m!Qy^e8&i=F*1NmM!dx|pVkSj4-;%!ClHPqa!){p(9i-X zX>^ zw6lV_59e7Gbam^mfSzHMHV7pzmLtHRoIM~_k{F2%1dDbl;(_5F($_iw{n` zdzlNQLeW>9mEaU_YCyEybfp|G9`X^Z8QK}y<71|#?;$?LnKss8_Rfb9=RF{bb@*=%HB74_I#Y#63_Cp^D*BJ{(u+GXU%HV z{{ThVbklJn3#(>B>NR=x&%C7tI-6f6^Hd8Az1M%lH(J5@StJnQ1pu6hoo2-vX)v=yYB+(3&yD! z`)r0M<8RQl735dC&gTehKimXkJUt^xm=qBf?Hq*4pJ7|j9V9QNEkY;=5$=>oj`CJH z(!dMaCaT!vq|L3`5b2dZv1mIYDLN!FiyByT)>qRvxWIc`+1M7j=YdNb5A*}|piR4$ zBsL^#Tc$~$n+EY=(|F}@p`XF5wVmg`TCvmm_IVz%fQ8kH1q8^%4P|3PVYE?N!G@3a2b>hCfi=%n$R4bt7}Jr*l3TAw|yWe{2vFud!YfU zQ3MI77M-Lzz2U5)IB{8}B_+p_;;qlV2EuWj=Cc;DfbKt@MwOPJO7YnTiPbv260%Rq zYq1hTyU~_fmJ0%9q_jtS4k`6g{7L1)i}~7v@Frxsq%1m+aPZ7)AW8%|w2v#o3g9fFC!Rm?8sw!xl z5Cy)tz5>JFJ=+Go3X}uUGlX&B)NZ=UqX^@hGlMubnO|P^hif6 zF{Vn1A}XYv1h2uGMNJG}AH}DzZ*32_!NMagU2tfmadaMUzJ$f4rwq{d)A!d8P^}Or z0pJh20fDX;g8XaM{(K5F{|s}ybjVK~Pv0XX-uh?8iv(%WBNQWL-okGt77lyx&3J*8 z_Xfq$%-A~PMRQ|h#)}q4{|u)-;|2Dx?juT%2T;3RgFJUqOvend3u_n0cMu!o|B;wZ z28kqq#GtrS33;katR0S0L5w*dzj$Ik*n5MBoJ*2X?aqJL)NyhCn{Em}EIX zksqRGCxCq+o?~_!D_NUoY0)uels7uL0kl#VK z@MXN{CM}!^GRb(6EM@jq9KA75z35}CpYfuvu~vq&U&aezYHjzNWq^k4Cz4+*%?Q{H zMU(+T4N&5w3!;^vzQFboRhlK%-F&$Z)# z9!cre8~Vd8tW}In(L=)tXN=-->sfs`?mO~CX7X33{TB$Um>;5-0)HG)^w5Xj!?_*Z zG`jQzW_ig4dWF2@Bxs0~?HXV}d#y=JAV`>h?_(T~U%@yEzs%S}Z!Td{R=B#J@Bxrh z343evD$8Kcngse+bSlbHAxGZ(nIehM5O$nKGoewpOjR;K;pqXBX`6O7{LbGgg?x(| za`s*(E!s(ICSaTdoq%IGf_Ka+-=l3H_GUN@Nw&IOXJU8ru(v{J!nAbQ>0lu<_B7?j z*h7n@uSU|@GE47bXk#ic)xcIG-6!KFjX$u~bv9Ctr|5!CV0=a1Mpm?1#Ux88nfjBR zztVgJ%Zm0x6LgVq01%BxjZvF^VW6w})0y#IwRKd}P~Z~#=WAS^qBZ_+T*j2cWhq@6 zw))op0~ZhAqEzf&R4-lngq3W2|5P1e8H0!x_a`92|iIIOWRrKd>Ou)V(rYUjHN5IFRx2z5xRj^*#Vg9Fo>szJR`7M4`TAtTq*d9+R2%Eh z3H%giU263FP7TJU3#{?dgbTAbyh!>UPE?U5rqaXB_3*CJ4#?a@fz z-e3A?CkFm|T0x@>%9YUqX@hE$HR82pItnbs7)y^Rm?JI*AT z>xLuJ!+8zfZpIpWT`ur(Trx)1!*JoMJ2#4Va@9J}c5@hT^`On><%wc0c<&L!$%PpI z(tg?7(I0uBHAQY3QP8#zPTPd5W6i}a(?*z!^`_6}obgzU*!_szPo*S6v%3WzghR36 zSWmeeou$lA;Lt~%Rqu*}!#LGktdp_^p(1L-S6=sE;AL6q3QDu^C2s-rSi>TK652&%F_5ZE7`aJ z@55*^yh{UGZD*>1r29Q4K*X?UpC96lSFy%I-XT6@6)ss=#ciuty#OO=LiazaWJ}R(!}J_g%qkRg4+Ss4c92k0~j+NM7GomuWfNZ8qOzu#_H7= zh9nRYw;|rWkl1yFfAbkMP?5v=h0oaA6{3O2^Y6Il8rHDtS>-N1byLthnRi^nYF6${ zV~DIr7Ea+K*1+&J{X3q%hP4z7-|;imoB7VA&rTLU!jX6jf!F)j}E@EUtzT9;{>!BR;sis((td;F*_-6=*85ec{jibzu z7`Aip6K(#xiZPw?=g1G9Q+}FB*2?)GW9{8man7LZ#BL1W?y$C3_APq>7y|RNcx($vy?Jlt~+KAcW-iNFIbCs}nx zc3yP@t5fy3otBZh^Z3YL*!Wu;Sgn?xbo9^arz``t>k(7B-?a>GERUtk@2OoO#@d3W zplN}@hjyO50bJ?iSiW%sYgY5yu~dzFShC`Fpt>u?8L7!$*FBQ(-*@LbRNZWopm@Bynou z=g%fOsV}p>>I{3#<=YTxXojtkkPW)QJ-pU@xzAk8>T?N}YGhJN&Y!2ut%xP%^K-eq zjk+SmQ{5DME)Rs?+1H)iQ(mVeuL7*y>2)^l^Cb)HdL7Fdz18LPR~27*w7MZ{n{PiV zZBH_$Y>Tb!)rg`6brZTxwL$5+?=V8;GiSN+OQ=n1jNt3Pgpo#%-8}D0wpZxapU?dY ze7tUdzWpm$>Ay@t)vP_eVDU)?8DOrDQuwc5vG9ni;}LVD0B7TsL;la;CCfnfy*Ir; zi??_3n6Fu6rHwna9#{)&i&OdNuUTls0XtQ^UqmQKoOpViTrtZ{=SpCnY|7@FzGlJt zPi3l-y5}xIQ`cTF)_Sgu0X$`{pv6LMcwnzjw&%6r1d({rM*-W`Zui;+)UShdM+1B8Rv8hP4WwG7MKa(OFx(5~3>D6y+8O#!KFLhA;Yt z#inQGpj6Z~iN5B#$?B(@5^B-Xt&`_P{@cD~^aNmMOdqs&v1>E(u zXdMW_X!;u;YDxh)PHL~ihkH?%DVqvZ$p@4Vl}x~FUO;Y(?|u4M&)_H&t^064gzqVY zxO@lorh5ABS5qB$ovI?>dUwB+ zNli^a&CZm7OcBkB5v#Q#u95hu2Y>M`Ya+VNGd{wP)v9#Ks=NCc#z$fhPu|SJ2R+yd z6x-z)wer^Ul$|bJDT8j+eTuZ^VwW+7WRa@!R;bjix(oz@w4qr08GagjH9-9<6-YPe z^6#_q$n*U4W)>1_Y^2-*qmCN`Zt(P`OZRXiT^+!1ndr(n$b@K;V@bGlO{YVswMcFf z$V0a<|B=pBz_dTgK(qGPpMiw7Zgb>>?qE#1i-@D~g(!@Te7UKb4EIkRJJoqQgi;Kw zyL2Is&Z^shw7|9sVGHDzpJN_$&v1M)U%7?VsXrFcs*n|h+wBxYQV@jsX)1KySkBPP z!7q~dPg_`nx;fBR!$=kJ<|3D|E;RG4wjgbu{65kGnX0F>E4J`vTUk)GYb({pt0Hek zCbRCuNBBd)9{8$2N`gK7?n)K#Yb$zQ55`46}q+LH@4xDo!hN=dJgUladzh&x3d6V zVLR?l;EKoGwNBR(OyZ{#apFLwk^^ojGY;L$02fln`d4N>{lPA{*>iI!eo zdP|(&ezL`wC+)^n9J}KAH@n#hVMYx;c@KLf{2s<9+1V(; zThFiAS%-4<4BX3tnwC}jbz>P|l5FW5_!`}EP0GjZW!ZsGU|02jR;cPe7>XTUgg#Y- z6+Fg}G7ZG@te-$yyvLhf(|4>}~Jm(<0 zDe!vSdWanoq)6WVFbhwAeFN=){)x1&Qk-bbOSC@u^NV4_vHqt&DI^1}18ETt(Ogb6 z7kN+Zhu_etO$z;@ihwBLHubP4{ywU23&pPOkz_k3{jb>wei4xp?ZP(in>uaIAz~ zV)EhXhE4*4;^)T+$3R8%y^j4Uy)R$}^EftUF)uvK{OVaNQQ8m>tCCuyB-S97{Ps@p zkBPU$L3NVL>hOWNtZJVzi<7K^w5J;s(jM~-UPwxQyW$^XK>+!!ikEx9qWy2u;+K9}~(aF|b&zIN6r8AddYOCK%XWU)B zb6XLkBH_U_!R6bcC9dtpS*OPFw$7epkc;a>dtz>U5JMY_#+p#cD7js}-CpNwLHCs= z&dU^0ET4ahHJ6mqPvaPu^(>xsYspWaVv*HX#gyO}&HTCXbZt8kNeh08Sa66Y-d=AJmHm^^P6 z)nViqO&AY6joYaE5F~463AKLAZ5|DXW#ZA48Cj z;m|$sD2?VLztx6t*J&10WpFb$#XLpVy>*894f`j3^j9D#^znhq*UODtIr>P3lqm0w zexnzFTkz|AZQb-iWq>@wKEBXSN(WZx5(E*UmbUp$fTw*8BAab+JNLUK@R-YLTS2*o zM*tB+^oq9X(@brvsCZjk4zi)GI3zt;Q1PkZZmYA(LI8r~|Bw}`ar1E=K3-GpZ6sAj z^;kHNbsS~w{1Q_?FoKq`EZaS*46P+-9f2Y-+qq@Yn;4*p$X3b#TB?v*b?5nKSC>uCn_^O5WCG9cr-N0unzUCybeF zzE|KVWheCx6TJTc9#s0+y#+`wyc+^wPTWdc8-SGiEBWUof?oh0SOA<=1P1`M*>I3~ zl+Aa3H-6|G3#hVzdIb3nG}Ulg_hGnTuDrfV-Od zH|DVy$|Mw$M2BBsb=@Kfd2JV1!*br-3v6y!P3(ju+G=<@D`2B0-inUEfrDvags_t{ z`pC} zesWkL?{%3quVMqvTxBcFcEH(qAl0(>hA9{bVh}Hm@h!~Kj^ zWG!jrQiuB)#2f0JA<7jQxYPN9T)QzQi)e#w03KuL5hdciI(g-*tdlV1XWsKF>n})3WGi9TBYuyb71q6BTtHJ{lJ!S} z^&qXbm2*1r5#z`f$dZzg(EK-2c3KkVR9d z(4&T^L>JhM;cNhI*qYNWb;`s7DeElwi+Prx2D^=LSv!X|4%L3_k2egZd31A-r#Za} z#PDl8?>cS_c~roi*KsSy3a-7u;?vuLk`2~`wT2^dXneauX;A|NBw1%~B<+yc-zy>J zx5OilL(y={LGV(h7SY{oRcIp%BKdiUZP|FguE-jnjmP!8V=ch9gq3vQY>?4gwcV!sYVQot2bNd^X5J?>e3y-)?jv?y!QM1vW-f7Kv?1 z*3lVO&$>VS5!mjI z%Z2XEI)k9H1X5zEgnRnf0ukAFhx^7M=8gF*+6?*yO%O z@8a#b!=+o;hb7%%oCjJsmJF8=LlwjY!=oZXJ>*{4nQ-cfBBr*BkKICTDP^rw&`Ow3 ztx0W!Q>bdo;c{oCsjO$?q0CZSO16+DF!y_9EMOCCEY{QBw4Pql7(sK64~;|}l&y6< zN;-Vl+e44R!^+r%#u6;(IDi(VdyGN?0%g>GkF`FA~AO4JmOM8ziRo_o6}YS}DQY zIuxepdA!|<9`sFayCTUJ!XMpctr|xGu$xcJqM;u^e&Q3=6ovWr1e-|YFMa@P^CQ1>ht=ra7YT?1(EE)T z*pm#YUPD}Wy>fTr-P>%6FdE%y23F6dc2VGKyf z`VEXS>n>k$pAEtK@Y#LbAFzx2{s{Zv(06#-A6e`AXU|b-U*>>6#aj>3w62ypn9BUS zwp!*_=lJp;Aw7JO&u{$*8TG_9?*9|^z^eQ4jz6*N8hw924O4NHPCRO?jlGGgvi=8t z=O;F-!5@W07c`C7bQNn6VU*YD{{PaK`tdUlfOt$efBb;e5|2J(e6k&?WN!?&_mX+^9r(V!VO}ncsW}vBulP8Wi>3%keZ-8lG^SMWA`@0HIP&%{72#m zx47potOOAmfDReL|H*_`ZAJDx3J?cLaN8KdoJ3N2t z=yJ(vr+Dr!%z~-?0=l7+zQyrzRC33q+@Cd>q#wH`_Q^P0vjo04x3k|J%X-7xdH{J6&Q*xk1>*%~#uC%`oE;;MC?jAF)XH z(t$!BJYvb^yltIm?SON9xD&TtTfXO;oIs*`b^hFm%(=YTudE)M%m@C;VuNz4(!N^n za;23Uq zzMzc)k2rts`#Y=G>90r)ZC1kmpI)JbIl7nRBgnULl^N95UJXFn>h&ec?v}wk(wo9P zlyE(gYad4P*}t>e4OjdpoOFT%6|RyS!x&1XNPgsZ);6pq(!*8-iaIaHZlt2PuySGR zc;I7}D1-WSR%uNUiL67a|E3+m3Kd0rVXJ$%wu^`P@HPYj5*G8l>YOQ%}Iwvgsf{ z{Rit1wX7xRsTraSxS~{!R2%~%mZ?3wpjs}pwr3D+LuFgJF!P5@<6#VxXOs&wqz+*kc`Sv9 z$4FQsy~3b)x8!_L?tmg>L%9s4Kw6XwQ-Rc}^BM}Sp+&bS!daet2=_x%OE)~l1B+Rs zPEU7}9I8Ac;(!y+h(O3RYW3}2rboOkd5t0}9*-_3FCUW=endmd9eH*!813fXd}}dl zk{)-GhJj4PhD9K$&*X{7jHPP20WTxVaY}h4_l_ayZkhph(U5x^xPu}02E%EcG+nNV zuUqbwE5YrGi#E^l@-W63Qo)GCQrQT?k_Rhs5z_**!)>CSq&*A3|LmjwX5$$X_{A42 zw$cnpW>oE#$GOi-R;P2ton_T7*`ZY108+{aDUrN#u`&eh$XyC)L2fBT@EbKBa1S;A z_(GH)U-*($9(Eufb;c+*sEZr}J!veLU=Wn~m~M=z_(Re?9HAWSW+!wqI9wc?BIY7knn5KimB z>9UNkyA^4xjCh*D(B!xId#_lnptG-Df!hABS!ew#*44l3QILf-pB{=&N*z%p(R^H! zB>pHzALT*`3-_&z=tS#K@XDgZR853IERr7_;cZLUFl{t$FFwcDJr`^7NxMW%LUEf0 zX}w~e$wQ9N;OGen;-pH#0dnH&AeDk+DZo7pFzY}ryfr}UA=|?Es}h_gs`HXZyO?je z(b>brhL-d0aA1?_ssDHN(2m5M^cignbq-NSibjnGhd zT!*J?gy^t?bto_FjR5Dq;#T3ffvl-x&({b;m`^AQb=xOFXpX2bdt`txjR`g<#rG55Z=%BT3ldV8aM)3`6Dd#j1UA+0d!27mT}@L1^j34eR05XQZXLJJ}Cy8SJq z;LU`)OL;F7ipssp5#Y}cn}k1wnM)`zh+mp2gb2p(`ID&#?7G;Uf4a~}h`z#GO-G=( zh)2y3ItVW>@$oZI$Q7F%g}4Nv`5HcCw$MZn+~3P9_;=)AznU+YEi`4T)#y$ucv2dI ztF7iArU|WtTc4@l^~?FMn!eb`% z%(Qpp0{*(swRc`2tQLgLvp6NT2Ye>nX2P=B_M+8-Q4m6B+230$i~ZC_AwUqmnZOa;&K~luP^S$G=P!R1rrP&Dz;=ZYAI1wDLLk5O zt5AhkcqB+d=TZFCuY$?G{1>5%AoK{P_?AM?k!t9IN0e()Kv}LZ;hkZ2WSA!i8~ymW zCqgLy>ah?etbSX~@K&J^DF`QhDXODzaG)9*{HIV&5MEYOGT8h4B{UY>>cZ7F*Sf#2 z>Zty|+~41VRsTcx_m2B}HJI-!5}H?AmEf@o|LgEi?k)fL z-|66wiUern46z;rDP=*_DNEUPd$*w3q z;vxVgk*OMr54v@mG7iTMj98K9=*7l-P`v0Zgx}`x#sh9OC3+ec6U3f%+Ttx>gC*FI zN?V@_EaQ2=N+ejg^dYF>Eh=;c*L4Ae_iyr$E`TuXrai8UIA0Pp`MhMH*p1idFXjqG z7gSH^05MN^a9;Ja7zj`PIXjYBD^xu8y9k|HP!*9c9WKruJo~VqjCPazJJJ0e?fwpR ze^cDwWcN40{q5lXwse0RyT5f0+us=>?qfnH2cIxX?7=_ypZKjHeaS;o#ri_9tOhPi zMWAzo8u<6m6j&^rTCe)AJ>-i=i`xa~I(Pg7@^|4o#)$6YTvqVpFdlyEmUcUEfHE&rfU7}*|(;Pzi2Aj zPb);?ao4^kQ_NyQ_uahiM`DWbbQk~dBXMn|XgnWoh56s&sl47ivAOVc8Xr7QY*;A| zx7ty7FpJpVfAI%zz}=_7J1ftg z%=Pn;>%K)C`PO(Pun3C}Q#Y6ZOeVzTcRZ;TFJc$;wy_=%_I4wD718DJFZqCHWQ$=! zz!bhATQs)+R4Gq^mF)7ZufqDI0;uU&;MFPtLEq&&A5Ju-H%<&yfd!1w6HB69S&+J|Ho}S48(XIHIg7;L z>K!i9WM`7{q)A+`){i(!R}RSl^(ZBv-3!JHDZnV067fh5{v;!Zw!g?O~qpgi!AvdH6V zz3x+>(05AA6-L{NB=8w z3qSg~_^|rJBZ#c6=A70a$VF`C``3!+t84!m5&tb;xlX)PD=havzzz9^&s~qQ!VV+y zpH&#LiO=0Yk%#^(=SDvF3yQ?(_;=2)`P?rl5=Y7ZRlgx$;h+XZf(HK``6ZwGHAR9_ z{vG)RpSzJFVG#1~GB@zKo5Wi})4jZ8yI6~@=TpBCmkH^1KI|KG*4}lz-nZh0z@vN6 ztF%oRZJWhRVeS^3jutFl%O8F#au`AKw9Uw2Sj(4f287|eRsZtOd6zBlZ^K=U1W{hY zpON3SlQ-Fd0@v`bw!+&J&wi74HSfO--p_aNgRM>ed?KO9ZS%R>=8(+Ok>?BO=X1~8n{EXGQXb?Osv%H<&R)Z!9Mwb|T zF5mqI{>dK9{F^@E2lrsE(|@A!y9S>SzY7)`bm2qo;#=BDZ=$!fS!XFn>(!!+gIZPBXZ^;*afO&B0x}5<}8Oe}h3>g>D&@rAr-zy%EozY2QM%F91XSFkClC$zEAWlYAE_ku*R?Xj!9E-sV z$#hbewoKwX3;b|Z){S@AFD7)@KbE+v%eNCU&}I!SrelE>g}ZzoPyq<`BAB1YQ)v{f zPN#=E@x%K?Kf(74esjOrtmUl@V6aLtV1~(yso5^+t+^^QEa(`Om()gk2x&~!3|G!1 z#$a#XciRTu@qjo#v?YRZ!wJ|D?3ZFr%fI1dFb+G_+y)?)pkKD~VaFlM*7!~wP-XH~ z0!)|Cy}y-D`%dgsH5?)I;_yWP4B5(0eJ4%{y}bSnfG*!9un+~rZ?-59XK}EmCVc)u z0DQH%6ri|;I}ZZjHiVP{+_{;zI|MQt-qD_ZNPJh6e%z?k5+|oD+PfP?et&{cmzPWs zD)5{uqK-E^DqgOAA2dU2S@mlz_q7Kd6T_HrXeEz3F8Wsd{3`{WNncR~mw5g4rHF$T zzkJD`9~b*bAHzp-;p|vcY~h!D;0f`au43 z@o^WxkEYfm=B{?JsY|>0k`qin11{q3a%@ zH|o5Z7dxH^kTYHpBhulq9uHZ$`qiq2kNBIok?t=V$|%tB3Hj4)+N%g4@>ahpvD~CY zZerQ(b`!B|hue+N4(z}y2(1GgH$vHz)S_KQpYpAzo#TctNFnc=j|LR%Ly5 z>J(=nH(e34h0)VE`(FG^I5m}j{=FC@ES<`~|6Xj|DRn9s#Tm>6QAe_2qu^<{a={RT zr}4aSm5QD;n!b8UanwxQPl>;1h`}?9xF%?(7h=Pw^3GSqg~G=se&wpzuVUQ>BnoR= znRxUyESVk|`G?oUG1We>iXILVI6L?`uISWtv+}3c#6dzWBky@#98#~DNvZRX*&-fA zr$ZXbD6#2gAV8i#3V0ZoXY)(f#aQ9(DZKIxF*ZG++pE`)LFnEE-C+e;C>>$uB4)xY zpc{xF2%%H~L%XxEB)n3G#DkzXgey<4#+|0n27eBDj4PDQUQn!7*ERR>UIo3AMmtkI@>&#>&4*gE8sOhjX(S~j{|?lLgX zg{W!#47~p>h#4cGce*9!$Cpf^Wx!GW^Zf8L@!`4gm5;>ZP$UACI?DrK0tae0R#Kjw zl5^pGrag^l!Psyg>`*Xuz6`YsKZpq-%?`d#xiDq6?u+&c#pxw7;?jMEV3%)(psZhN z$;Xz_Si9l!z2?EEzYr63oe>Y~)tJjJ-%!DR;f3h*6m|~d2Sm-w3cH7bmFM!bg_@f7 zD;iBtCVUgZBPwVX)>-)%gI)hjDQhfsJ8WsIk-4i5uP#ub7v;Z$`S%qxX+oWNdp}RS z&?KDcWZ&$i>A{4Vdi%?YnlTlGYVGW!yeZ^p8~ZLFO+%dp*VukyZCjG#?#^*v`@18mOr&1V-~6ftpC+NmKqqpe9ThP{3V*no*UT zC4fQWti5(pQ~q9%CRV7|ly3||MvtcaLXZaHY7_o*kS4|}<}QKV(u9WvBX(XB-ZfYg zDvWNz-w6iTc1`%l6pr`G_62LA1i3N)Iam`cz|yxu2-1Dkm`8>n-G_~NpAb!FFZ&M! ztyyEfIz$r@QMoYsLZ{2}E(+=xq|C@1U=ZyBm- zER1i&M}=ydyxX=BhA7t7Rpk8cSX}BFz+Xzo81b&UdfE@dcMuB3z&UcvItQYPN0v4; zM)Z(A$vswj##L*_T~aV6H?$FqHA@q{Xb!dti>;d&hv`Gy;-!nTJ(}wF5e+qmnK1M@4{M~EDwsaCuV|!+7lhBB+V3^d zbQ6Sj{~u*<0#`-#{sGS&X6^+9LFKZ8h@iOR8ipt++Er0e(a_Xz4R=&1H1*;dh^Uoz zY%{XW7K_TfSf;q6rQ(ihiECGJK{Lgb_j~3%16O|i-_QGAIP=W+d7kr}bi-RRFO9Z-ue zFxEjy>^#q#Uolu5VX$;|&q87F3_%M#p7;mAH>V2z{nM6!{g%cD&@IB;_K=QBe#lm2 zcDc7Uhu=Oo9#>X$X~rB&Ud%VV5Y&4Swz7`O0)u?fn~i-AWv8}aInOCkvCR=(Lq?J% z_pMbt$$9QOo1>+iv5O~P-HdZfZgwS?P&E8qrD$7xx?DH;4vtV~mXmvJk3rD5L1Bd%hII)l0 z#=}s*3}-jGD4)tddWvYC2xD7f5Y2~X9?chPqWM-BJjMO0MRO_6YoeKn$cbo9LqJ+I z{XA`LyDAS2^5{_3Dpr{zXIy4q#^QopIEj_SDwE|mFR_?7<-S~Yk#*{>d@Os_W2d_- zXXFAC+uTEWNeAEUUTGpHowxlQuLLXdah3gHQR3tsPPU-l zO0EI#ylh|fQT*k0k);MaH=vdydGTQpaahKKeTq*T@H$$X#TsJAdV1XcTA858UuK_X z_Y#yB<*!TGfPOGgeu_=*r`&RK#T$%~{gpJi;VE{wKiY^JCz#6sCDJ?KBy26(=bI2+ zIe$;@MSHR5f%%J*Y`_5JGdZe|-5a13$?FSQVWRT9)69pm?cYS@V}rc+2wOWyiFJDX zKxStKDMK6las>B_+{1nIlyR0^WoMPy^&1vHSm`CN-^7*=RzjTCK9t#agO%&@{(J1h zB(!xtN0==Mnb7oz?Oc*F&mfn4ZyPmK$v4PP55AMEtdOT2w5>`;Tip1dvtt2mf5=-{ zbU@sHx{D`5UXN1lvTMVXbotPFmNH!F;v%`B`VWYCP=|dsTnY1x{=^{RR@U5-y9YV6 z$HYz#S6-77Ol;6g$`Icn>v_!W+E$r(vVFA<2EVGzFRW+VUs9swqcVH^lJbV^XJku8 zD4Cw7y4p=k8@8@8pU=ma%aqU-9dGh(&HmhCjO$@MEVvF@GCV&YE9Ohg_M^#q{4ozi zb-yXIZJV9^`<7wVI-Z(M3AtdKFWf*vcppl8l0-)eBWUe+VuPX`u1?a=cxL2R)&kt` zs?01OjV|VmyMGHm$b*ku+4hl2eEZQH<{sh0emh0b=q4e=-v7l%XQS;6nRT@}`Lo1P z%5(Cc*ICXeB_io-7YYyKjvw(DyMK&*e-X@E5KxyKpGyQUA@_iu+E*caHnmBJB=0#P zoYB{r`m)m08VMyV^Abi|2%~}3MtO&>xQch|Fh{Z334^o+`NONTAI?!B{XwpF#*Az( zpUCAHb>Q-0LOz_!{YcLJYzY!_7a@1Ga)k#NFXaY?aD|3Kp{G#j&gG{`ejasMTTFGF zSL!_T36QJ!ek=lX=Q4eT)IlLW#Kj+y-_u+!3)_2yd@qRgD)ute0@oDy^~|fHP#EE@(&&TW17%2{;g1$ z=cs@!CZAD&e57LlZvPuV49R(pvs6-rU4Z$8rCB{~-8pCH%MM{)0$9jLR|Wk;{8(a;v!)S9pT9gx3L0f%oF86@(letQxuR z|H^+Q`9K~3-$D}pW3&LwUAg@aNv=f@+hcHfTP~lNXVzjkj}$s`0|7!ITo`D=4U8oD zc`gqTa>vUo#!=qrf8{|2*7hf-1}uN9;%{Bg9?>G=#K~e2v4^&Zc=2cOc$E(%_Rjt0 zeqvj`j@XiWP+!CP{=0~S<*+(B8tI@^4X85T zc!!N1uQaemevdf6zeY<+Gi`R6IS3o6z@L4I;PRBvLg{j*ncxXQ7j@&BNBkumMuKzq zAqDpwlcRx}?m&qL(QOAUWk(roW$kstQnHE96Q{*{lmU*Y=tsk z;2h`r^Xf;Kakltr;$-&C1SNP3m4Edb<{#dv&Z)#I^SCvl{JZBR7hsmG$fGa5L$=jl z?U-$=@qKhYi}vx`8dqc+7**ym@35AyU=J4*7PkBg6a|jZ9hVCSYGGpAxG|VFv+{3) z`JES47xwWhih20wZ&NM4E4-Y+dS1R1{+U{Q_kU{fM~HV#Egrb~e`@hN-!jis#q4Lr z#<2ElZ#@(1gH`6lHLPo@;uDBy>XnotpjT4&HEc$z(p0$)+bnK|Q$zMks^T^DS3nef z#=t7`%(q3T@il9jZ5t6HOxv|d8?HjL+j$># zEL!4($9*4rbCS})w1Mj*6A!O+3T&7dmwOr?>d3tV4+gH6dljwdEo?9f9~FDql}XA- z*=05BIaz5ZfA$ueJz440E(T&~wFc38A5d+MbNl&9K1foOZsxBwQWCW3qPZul%v0ZH z=O-(JN0jb?2i!Ng=iNAmzl^VVO30t%((&tWLCzC|{%pl6gXFOO`(18^IxS0tnO*o%>cEZ;)>&=I8f7F@jDg4#t!5_rPZtk<|#z#5WnWWjNy>8qK2DDY)7r%s6 z>H;;nzZ>@EYx_vntQ)TnV|@L0a~Klq#;fil>+xp3I_r{{4CF0w3{uRs>H}S zZ?Zj8m6l2X{IY^V#iy;-)y3j@=;1nT5c@m~;_@=6!#mK(yLxZ3m}yG$1~FVm zE8G@ByX9^+XPOcmv14U24>U4eEEJ|&m@-n)}pdq*ufBEuj!~?$5*mB(~;O| zD@0xOgt)7!{V zaW%N|`8sbcXIp0~&CE?9%zcRXd24=e!>`v4_HZVK?wT%VO=l_1g3cD{kvhQ7^31=1 zY~dX{d(3TiewJcZ4)7e~b3@qs@X;Wv+Q&5EV|o!gHA`uz46WAlNLRe9ZMh-p;WZMP z!65dj6{7g-BEnSvo-RYsT0=+|iVOEkoxca$wRk-Qv9~3^D8~cB4+hC=u5xX2h%G#< z+y^fqE#Gl7)CYW@hkfUk2-{m!j_TR!xaMAmJ^NF9gGq0GuuLB9YXUbBg6u#pMsdq| z=FUazNxI@AM=fLC8A^D&+uO93^#TZ_7eVs+`r2QlpqjwQ-$J8QXq?{0re-M3%`sbH znpZ2BZiaJsZoZX$lc5Co^@OH2h7M6%;j0~peLpM6P<*UA@X<2u@pBVS;0=3c;gcLU z`62;#F4L`U6yk5Wc%Bgd%*A*J;DLTF#9wgnC?Wobi|NtsoDi3BaW^6Ez@uEpD!hk? zI5z^ZhX^?~;*z$|gVksu9>c{3A)dy?)D#RC;+MGi0ybzs)c0{QJ@ou2#GANymk<|o zF@L7%gSc}6PdS%=DoSum`3h}pGyhMn1axq;soDbjMZDrkFQ@md|g{xbC;N69D zh<1b6qv4acAib*0$KL>3>^m5Rs6OF7=n?j+@Nw;D_Q7jPVAIvm)V5vWFNZLd1aAUj zvTk9FBu~w$0Exyrb@h#7?A&WglfEw>1BXu^!_s`1-N4P#gY9Zzb`6MqF5hB8ggymo zF1(cJ@$+AycJF7Fn5lTl_N8n>rqUwhm!;Z>mgyeWWEnD)c17hfUijb^AFHjGUCF-B zR06}#X=6Wi_?sE*-h+{KTv+cfMoJeohQmb1JaxoPb|+Km(R?Mk&FNDuV;>>*xj4rx zNzA(2pCOkg<;HpKu)E{4x!Bdyefnkg@@&PY=}=uqn>mS3NWq7DoM1k%Z^Bp=47I(? zKAeqLG59EQ&>Xz&7*oNz%u$*|yJEDAwnKalJ~8S;U5TI&WTCbl;@^n6<93K&EHGd@ zL_-DpaE{{l{AQl8JZ(S4GZ4`GKzl>HB*j2k6gMw!wR^Y}tZ>e%3jcNitC*v7kTd^g zO1o3@L0Rm(dCDaB9`hbm#pNFBm%Dsl>?^Fve5IM;6_z+( zF&l=l3GoIK3qpD@fb5%S06?q0(FK_&RI52)^#^V`+=-x@i&)fRyrR3a zS6FlYi(6aPz60WblB;bfn;Y`PPCWl(=*&WHD3DEGqBNF|?qRE!Aa3@vZ1WQ3nEcyo zEO)6A(&*z1o-#f^2T@3&C`?)K7){&PVl+pbj_*1pJ69gC>?#)VCsEd4=kDGk1V=yu_g>-_>y5g zb3aE7elO2eyPjcFRw~Kzr9ar-mC9y0><>17l@jg8lYuPKY&l%t$4|4P5XfkE<%&|<&u=P^W%=99w!+oQ_p-bz%{G}SP6j#n083k|nB^BYu{YK#t>y22 zV79f&@A8+^Y)jr%A`J3Jqix^4rz|wc0V%fjAMh6mAB?j7@F9P96fn}({v+jYS?=?t zEpWZk#~{~ziKTvmH!@p>vpt_E&E)yRS;Z$xsORwE2D}aDJH+#OUurxWDDikPlVt1um4c;6%?Gojua&{_oZ89pOuDg zC%bEPz^lsKC63kqMfpX3Ie{Ji1@9T({+0!7MeEjWB3rptY3KCMMwxBhru1M_wkma* z`B%k5-jK?g{EDuB(wl7fukgygVLscGOgZKi_U1MvR5pCWe%hwIA{R_x!P}uXVkH~2 zU775E7-M^UBFc86-x2+29y_&NY20J~S4i`sM^7{A?^A!qIqw@)nHP6OpV++U(UT0X zed;IG?D1gdmX`bA1!_ipzEwuVK6S}gET#w%@fpu-MR@&vJdgcRq`WM*SjM7vpmpgI zV;i%BR|HE>+m)S4vO%7<43p4MXXdYDX}gu4@*6qKwj0;9c_ll&Tj?h+&0!IHl;PH% zpCM`)3Ajn8tkHr;W&J4Zwn?E z;ng>ed)50;FzE9yP-OPT$^70@jDE)vi1@APYB7Ilhk0bcyDUns`sJrg-Kz|e$A8L( z?Nyq~=NGdjdy&SW%h`s#xO05{96PgD=@anUbG&3d7Vk#p;Xxv=zc0SvjGVNu7{y}u zDT{*kqj@UFomwsAC!RR@f}fzxRNq01u@61lgX`IYeM*q$=8=zapV`|lub(IO4lG{A zniMOGgD#-`;0A(@@DmsaD&i+F5cFL-+gGe~mm7Cub@nS0>#nVQTvg5&pyD|yVT5hr zekIcBr8{j=X>-)*b$oW^4FNg<=#yT47t>zyR0EiNj@AD~k9;WP4~Xvnkhvek6VtmN zvW^FpPV&SL+0=u|4tZ%Li#(*j|(dOm&iT+OVF7k&h$Wuo;KZdh}|;vJd0B zKi`^NKCJAN!&_sjnbKLl(289uQIgFi6^~IK_c1Y3Zq7>vjljHCY~T?kv&rC9DC6jk z<3M7Ur5a+hr-Y?t)aN6l`!RBXZOD+?HmlgJBZ_ZS5R}q8*%v;=UJSgL8lzq%lrSVMF*>{9zzjZI(PhR zC0lS*@%G8%ir?^EQ95^Afb(=rB`hgIq+-Fuv@I=Om;@FH zi0eU{{1v`}oj;3;8W+mk|HOPFk5Ftrq`VNjw-Imqas5S~%ebNqQE3+t_L4y1?3l^} zLGuCnKQDbTZ2s|7?d5Jjom}??P|-dy%;#LTQgYmHFK0o2DFL!qBli4XO4GVdP{{p_ zzrSrC^wDxQ{V%0ez2YhKKAKOCX&*FtIotS`;?u~2(!mw-=llE$LHO0H!FdNfS6zSw zB!4L(bzkPb$zs5A*5Dla9xhYZlgns(CevHzRp*ojb#`D`34dX&X63SP&nX>RFVq-I z&fsAv1D~%qH|Gs+`=G`k=&@kw9-m+yB$(d0%(G1CCO-*g1Iv`=L0>Kt1@=ABkBR1R z$`c>KiprFx^1rd{YMHXo=X>6^=eYkM$d7_-%3%x7D@Wz*ZfxiUrAN@)s558`^I{hA z6YTyJlfzH&m6Vv&Y_|CVnxpJg_SXd^t=>A^vG|uIdOP*x&qAy#d-)>j*QHn3!i!4R z2Fn95kE5!joR5p}FPfRLW6-gS$|t=)L`9M^+TuA6*GisyH$21OTLS)C*VSdOIv-*~ zd42n=jkp5&?77cnW91E#ut!Jl_Ew3ow|XL*~4=?*)>)%R(d~*>?{u|SdiWk}D|E;W3WF>Vs=8gTeQ4M6fpWu0MQ9s+oCyJuTmm9OI z26c#hXgiCQ)t}{~q3p4&Hj=*!WeuIwwz69#8|0+6cj;@CJT`Ay_;z2m!buHo7ZQq+ z&U63r{=+KEjw{&Od-0Z4P_g8_nbn<>JWRYoIqkJL{btGYT4;~l#{P0rE9-Xp6*qW) zbZ~HkzZPPusE1Tgn|5Wz6@?Q|eNPIZH6ZO0xT5nFwOH{?7l$*frb2E<3A#*6mYR6OpF@wh|Zh zfuU}X%{9#Xe(Y2oH9{VFiy7Q_>}(y})Zb;f9VtD~Ld z`nPO>ekvx7-ic)C{%W|qtBk$xuXZ)`WqoFx}@D2 ztMl;e=eBkM>L90}(z6e%;`s(Hh1ib__x}h?R_7gBG-bb@WgiEte#o|8g4K?4m#elr z!Rmcke!UqhZ>0VxyN28HL)9Bja3Fv*iBu{K5nJ%ue<*3nurX&Z_8}0-ZIqv?9CeHuzy)bBlWm}JavJ` z-L_5Ypw5!z?Jd~3PHK>x=)<0NQuoQHnhTuj&8|GBzT=g-SDPW*h;P>ssr5BS?X|6n zQVR@qN3_I9K@-c^{iq#@5v>37>N;zLA9{GIeMk|%1N03+mnT?odI(MgI9`I&R&XN0 zaTT0~f)fRfBslJZ6AR8gU(NEP?V4o^IM)T|s^Fx8^KhJCof51xuqp&+kKkm2b474| z)Hu0WsOMmw7R+^mnJt*V3ucaBYLiBz-M9O)!_n$c`IL{XX&2SzBnNxh%DSnq8~S`) zUyI?DEm{nnVU-Eaalt|V$URSRb_z~1IJ*R=KyXsP*;1c<7N;i4ruw$~aq3M&q>m@R zGd;ro%ILY&2e9CSyg0=-8~7=Ia7qxT$vExpX*0*GKN;jPWwzgYs&+-rJY(D3M|E~` z`uvH3?dYeTmfM|TAM{r{C@l+cb2$5s>dUV8R~yQWwlMbrY8$5rOdjqwKuwjG6xzNV zpq?=}l|3-n-WaIjUS-Yyri1h_7Q5FJnklwN1}y2na`3( zsxQcS`RwD7>Kvz>2NDY&r6$NbKVnlxsb9(7Uozj9(UJvRXMJBruq)QF{Fl{5vdab* zo}%VIdwGtWHc9*ZB1+w9vP*xnzl8V&L; zJNsme`jX42Yw(0OK6Yjrt0vZ8V&{#*Qr~e`(f!>=24;Lk4R*_fU{O&l77kV! zSmZ0JztbRy`r$;4S%aBfETxf?zyH|NmfVL+_GeCx4ypqWv&Nz`kNG@R4-G8;6}7h< zz*zY!>I+Vjz~Fx6eyiEQR8-+WFczh%ZJqu`owuc`@%6sM{=D~Ih%%&mY43X9dYidT zR6qB)_84X-3A2Ay;`*4V4v_DzX7wkjV?1ynNVA%wi9>T52;rhh>Poje_@pPw1*I1G zCJUa7Rz zBm8H2$6P{|&hoIVwKx^US)V`tk zvX7^$+srZdpfK&35!>c>SbCaz$?XHIT}M zQro+Io2&V4fMqt>wn4MhRD*os2HTpBUcrzz*o+Ldf$V#OEzM9zxffnHNa-HsCTu>k z@jAPmp{B{v*V))t)kE^zOIZ8Y)OPZ7*=hh@1hks1u9yE^$d1fLf~GEDw`Z$nuuXH+NRX&G>htc`vL056 zaNW;kG3y*P1d{wYY6QssIcm>(!*I_`zlcxPIQ78xYR-wnSi{%Vcptn&$c}#yz38D+ zM!jWDmMT04EWe6Z5OZEvlN!#?;_u__<>seVR|nxys0D_I+-F7M6$g1uRT6%#usmhM-QRx*?^QO`QVceUm%YwtCxh!v?`ia~DUv^)l?w1F?&Ymn% z=gCLsu(^xX=jC_iu)@VCieYouzl+h(DRWq#C2D~D_iXmc65KEL&Su$5)R#gh&gLPe zUoH>E_jY=>xY;}U#;X{e#o%LIOV!Wi1q*B&m#Ui$vSAk+pRF!v)ysd@;%mt^u4sTqC#$xHfQ|;9}t7;nu-zgxd=D8{8jo zSK#izJ%w|{XRN&8Lg8A&JrCClZV=okxU(zOXz3zOuEE`gtAwk9Gpk&%t$t>$!^UU9N7GT~62*=c&&dV%{LV2Qa>d~w>qRMamN#gX#e$iu4eW|PsB^1SpMGW5l`p>f(| z8gvq7+h9n%?ZuWfM{zk$hn;>)(3gw(dzQ^*wYt(Eub#=?TZ6i}>?hmVH7XXG`ORQv zh8uFgCVYBLT_w*L$NpsMUHPTywqtA6VuS30m!9vbUCh7TlCb!kPbEvnr$E!g@V&iX zPO_WtsSUl)K{UZ5!al(xsnsXid7D=6-(rp5S3|A)fbV#W2p>?vb=q`11&S|G<8}n? z@MgDpN59z8Sp5!q360$gL3$rK-`i=&1dGSCV)zK(!+k6~$FH&-_LdVo%FPWlTjlO` zK=Hyl3EdO~ao|pz$1t}Yh{zE*{$;CxJCYR=&!E9j1a%M4=&(QTcnmuLw-b70cRZ4V z8f*My_i`<=!@$#Yin;8*#!KeqLhM9uHfcf%G4bEB{`-jn@YgR%HakoO>sz#pkNiZ!znKs=0lXfI|==`|@@e32R>S zRVqug>H;xV07k17*Ku;lQb+Zq+PI6SZJ zIdba;ID0&nUPOg}j=7xcJQ@)`3l<^i_z2WvG3H8fsr#Rrg)>2yIXyq?AH$yW$M^3( z|6qRcp3`T-+aMwE6aF{%v+#RfVXUYzd#;w7J92Zk%iUXnqGG&S1^Ca11G{(3LX82V zCla>jpJ6-pJn{^h&2=Anwu%GYec|?(LCJK|>K1}U=9FpNcADBSfFoOqcV9Ta<#!lx zAECwHv(;+Q-52&Z{JN%I+(DNSzP)ztDNjBD*%meBI;;DU>K&V^c^aXnaax{SvggsL z1BgL*8pR}A1a*7QKgs(M$dTO_u6`NbLyOhbFIx@2!Nz^0hBhCnX^q&m=S*_ho-@PT zHv{!AELP}*d<*PSb^yWT`f_`>6dt8YFxs;tDEA+%`V+gyn17z>CX0i9TV66WrH9dxzss zFu~qqNg34mx2>QsA*W->PulpU*h+(M>v6d!tmMuA8(lI#_-=iIITxxg)HlP5J>(HL zk&yzv`w2F>P;DvSI?h%??z-*=ysi8_$NiljSXm*O-|R1$_ZD@z)58r0cKCIrA=|x0 z?IW+)z&w9ed%E3<({BHmIB+A5js98fTKCD<=pZJFi66JWU>ko{m&;?n;M$GZs9)63 zhPz`mFJD!ecj7F!6yvo|Hu9l_Q;1E~=eCc2QQZt~jn?z7$8F2lRvznZwyo$~b=$-E z{b*cR2%Z<3b%obTBmo~Z>xc72mBx(G7}@<;(|PdZH9W|)0-x(g@+XrGhuMMcYG7l3 zVurwaaRT<+ipQQSmfQw-88HBUtIS(s*!XQ~OkG!p(5A((f^DiN2>;#T+FJFIG(>L35(A$%dI{r-4Na0>YuX)zJyB)$>0&b#scj7MkdJLqJ5;WwxpV7(9|==Cgr^@YONP zLG_5-;CVLSuQ~yc^;QMmd{0T=F9TEk6FQCwQJwa9~-1FV;5sjk9fYw{tG~m zPeHVqWS%eW!pm8pUBBn5D?k^;EW%Op=(!_Dov-n6)5n%I>eUmha|!z6zn);jIQ{Gd zYkNcuV(*lw@5+B3=b&63G?4`#Ro8h|tix;DM!BjTG{^nII`;cfbUTBZGnZrPdjHwY zc=e3+ShE*Bn}&P=hFE!WJA&;whHh(<2)61to@ub5`cKE9f3WF)>-TKRW}HyJcXKVl zbC=fykJhSl32S{)ohk25XX{U@Hu+d2d+C%KBmX{&y>|+|@xyJ{%cW|V*Or6)6?X63 z9i5%i%~d;bRlIhXu~M}pb^Kwip;q%iUmH#yM-gi+GIHMh)IumE*&sPiD1HRgr z3x;t)M76-53tYJ%k_$ZeSreRjyr_B{W`|F!m9qJeZT}zYWm&%2&{puL8ffrM+lN|+ ziQqdh8qnD@-6swolFxH*zMozCOMU8d8t>Dk^zeH-V$(zH?wH$M-e|A7JA*wqr)J3a zCb6_Kbz0q%nBHp1?dNQ1f6(5&sqK21y39#F>0`^hghv3k)33ush70a{nXj;ISJc+l zGBbt;!Y^pYOWN^@cC>59YufR;cD$(_E41Ss?RZx^-qVhi+VP=we5@UxYDWoEBdfw? z?TBwM;@qemowcJY-$=hI+)X>y)sCLpvA%Zn(vIHR(N{bAYsWzC7_1#bv}34t4AYKH zv}1&JY_1(!YRA^vF;Y9W(~cdyG3T@@ypsl^wBz&Iv5R)>svTpsV|VQsuN`}7M~ims zqa72pV}I?Ks2vAs$0Y4IR68bX$Kl#>gmxU|E%ih2DH<4~9mi?M3EDALJ5JJ$Q?%nW z?HGZ%URB}#+HoA_wN`~s(2l7M*k(J%=DXBqp;y%zUOC%&D~h+hVont<S#+TS>z?AUT+HLe{c*?2_?KQMc%-9; zeX1y~aZ!esWzkMgMmFN+@ClGzi(*c_%-$_m|6-Zf)ex67Oq}NrIlFA1U044y z$T^;D$xSt8z%oxf%T)jHbztn@VadI=tY0j&Lq=YG6&ug+j|S`P5c@cAR%t@*Yr;Yx zl=n1d^=_#_^$!I;!VCPKX}LpEaSf!{jg49CEp?I4lI?gh&FzQjjur&c6GPw0&i1tJ z?2lV&lQtc?KdPz@sQrFnaqtcv{9qow1<|tP^h<=Z*aNn+q6!t`+1pv43bmWb^H^ zR%edACyZ4`RWBR+N_e8J_!r|8Tk+SFQu9UEk=xhoZsapkdX^d5x(TzTLjpgXfLAUh-MO9P4ru$yNUim zw1TM8N7(Zw+MH+<(Ilc%KzaDmTw-JseTV3$M874vmFOX&Wkf58%6%zdqD_c)AR13J zh3Kn&OgNJx=sG(Z@tRsq%yp%_Pc; zkN*x(r4mSzex}N-RmFFELI*S6h(!IN;k7Oo(SVFK-Ux0bxJ7Jgg{OYWi$=sBbiMubJ% z;#^C?twuo$(6g4@tCrli77nb1Lu&aCtHvUHDWaMoQrNNXo( z@wz^RFIjYk2wzI5g%b(uS4m1OoJLr$q?v^EI+INpU1jZ;PuQv}6cB^2?&QB>!hr(f zW5A*eg9tm<6M zHW20ug!!+Cu-=r`ZaGV8>DvkG_MPjC2{Z9=St6p^WwW?F{$Dhk^zt_fy`=91&3sMJ^tS|c zJ|gV-ek<@e(kml9-eg$30&UTW(jOp7u=e}pivD*UFXW4$@g_p!BWV0^_;&tLefAQ~B9 z`^wAM+ECy5m=KiwD-zkonO!p*gX?Dze|a_E=I?D>>Duf&p|42d+AC@z@Cd1m7AIMY z2KpjR*wGMUSF7c$=nYjzxE3BaSM-Te<_Wr%vV&_gC#nVz99vj77*6uMYZH5wd7^ha`s-R@o#H$5xyRysA!P`A;h;261`8I zA61L5{44YmYw@#UMK?CJ7C)(%;AauvRS%%U0OlHI+-Gei;|gtswfCsMK17YgOpPnW!64PoiE#eTfDV4IvsvG=gX((I}$vL=%YW z_LBs*N+U=xj%XUuETY*&*Ao4d=mw$%M2m=)66LJ`|Jeyw5UnICwc$B{`$I9zY`D6ryQFvxu$*#YzN8+CYpVqQ{AzC2A)swG|PFAR61&wyddfpOckvf!?6D z6MAVxi-?A_Cq7ZV0WQ!n(Z~*@N3@7&NJrumEh6e486X6u#Hb(|)ya0Pg|UsNUw0w5 z5KShU+TGT!i?N%lb&!zvl=@3UkRc(pu%#AGbHH)X&nDboN|3q(uXT|304^XrT(aN~ zUsG_vy@9Po#OML>U@%G?6u5`VTJn&=BEsAtH((*m{U-sZ5!NHLim;x*LI;clSfvsN zMgj~}IAERv=cJk(;wg-Dz*+j^9+xsEF}?1syovNUkG?M`3gYp&KwBIYuT@ZSc) zP6A2=gn37w{|X7~kIlt|^{3(z!ur5NDPjGIs*JFUFeh2<#L&m*DhTTjm6e1Mck!Dn zBJ57knXvxY?MYaFjQ1ssw`$rigfKnCV!)9Y`cr5m;RfU(im*OZ5=+=jatmR+fz*D9 zgnbDo6V@MMQwWR4b~K5p#Lyp$(+KNN!I^~hp_DAb!4yC?;f91)5e^}|mT)7&`Gi9` z=1sC*c^Z?#22uzkTtHYKsVF4egycnpn-VT2+>CGuVSS*XlrSI2;NeSU#Arz#?1Wnp zt{^NP_i&pg+==AU6j3LhBkW8#im)f)&V+plV@8e^z7#@?Zln-FIF@iEVZEV>BCOrO zpc6}&wz{Fg;I zQ9vo1FmJB;Zx!Je1(enj9xl?4HlG;dNnr!wYI%XkVtnIV`xTH}CR{|=iEs&Fg>Y(j z;a??OrrS3XqkAl!@q|6oguR}GLkRaG97*^E!m(Cj^d?3k;XZ^@2=^tNMmT|R7UBMcR}mgSIG=DL z;R3>g2wRJYF_ai3gkL0FMtC^k3c}+FOEW}@ClmH0JcV!w;RS>v2^&O@#45!SL!mB; z{_tudoJevP!YPDZ38xWuBb-Iplkh6S4G8BG_R=t}zXD=-lR^<;f5Ih%g9zIRw`HhoVK>4X2>TN* zBpeOQ!m; z5H?V!FO9H5IE%1}@G8PCg!2iz5-uR@M!1Nuzn7MNFu5GnN6>iUE?==y{Ub^Q)PzgX8NT&iJ|zbi59x`!y?p^~sGVdqyx4j8%! zxi4W?!V$VWR>-4tIbn;AOdTf*oK0AnEAU#v{)9KYYI|>- z(Z|hNAR@{~PqZI(?IEj0axI!Vx*9-4bG2u4} zmlEy`%)^)L#Q2dEDhYp0SbxCLAL*Rm5Lui{a($q}MA(<)`V*QyZsAPw2$Fw7;Y(4( zI7kW>!ru^1CcK|;D&e;XXA=IIa5mvRgx3<@M0f+?lXOp$3W-rd3dMx`5-ufNMA%OF z7-4-R#)WVt$&V0r&K5cLCt+X0r?mVBGlCcgNFj>wPlPRmcMwh{e4KD9;ZF%?65dES zoA5cpYYAK5CdLM0lo2i@{0re?!iNZ#5gA245YA?@G6QBhF-K^Dao@5 z>mwujP>!ACdYy8n2)mJfCCT-H5ntHXo+k>{SIo))n5R!Y4Jd@;W2xk)> zNH}6LJyUuTV=XE0k#qiYrXpx1ptOPHGYLnKye;8El4lZ5Bl+`$i%GtaaHN%DT#p#~ zU{g23rQ{)pu$}N=-2es9j&LQ(XA>?a`{9J0b43xqLfBeF3Sq?XC4~uuBM7e`tdA$Q zARI;Vse~vrptdF=>l6*eNE#%*eV}7kV zFBdV=2Otwk!Au@}N&X_?2*PU#M-g65*h2Ut!pVd;=$Pz#1M~2uR8mMLg%ncoA)HC_ zVT7}$hzNHfoK5m2gx3=Og75~yuM#dK{DFS|Pon_+NTHY%ULssT@=(I1Bp*lEPIw#P zO2WGcJLicA7ZCO(T&UmwBZ%=EDMS(8M>vxr;7`~>@)3kLQ1|{K;bfBUB%DV0SHjtZ zKiBdf%zR?JOA1*OU;yDllD|i|nD9u#QBy<;+Y>G&`5eOeB#$L*C%JVQF;Yk&if|<< zWD!m!d1u1TD@1?`2xn7-0}1<*d=%jb!qW(^68csth!|0%@G@Zw1=vx?B%e!Ie-jfz zIGN;Q2(P6G1rtssc?#iJ%|7zK12Hm5VIARBgkL9IN(P$}-azt+gbN8zCtOU}N%v3q z0%1GH{Qy=GqmmRpChWXYWbqG#eF=X>ID&95!ci-27v>vHO|0Jte|pYtBCJ>5qOXKp zuUoqbHzYfSq^~z{MT8rXyqK_l7cL>}PI7B0G4$JT8Dae{YbUIdhYG@aQB@KaO|&FE zCLBWg(ngU0yk#oA|MBUsO`kbAF?IH=iDO4k=|6hv_}N%2` zr4$~287>Yk{kTy|-)rP7Ntf{=;5V{D$O>!8cztj~?f)IJ-)qV0zS*b&D=Rh*b4sK& zHEv~2BB>Rie3gxvPpkEMJJD%Ypq)gOL?wk(N5eB_;I`zdZgn;cH%UJ-)!W^ zdCGH%!1^!6;hgihzW(D|Kk)NdIM`qVyp4W!ilI;dPnfFA4MrX^D&2}5jFjQLwoz_` z`!nzp{2UF#mN0CGLgW#ngt^VthAqJQF9qlSWtjW>FT=1U3_HJs7{fCrLsuIvb}(G_ ze;DTe{>w0I3B!?4h&+lIAFVNL0oH#hIRCF0bASJ37`BArWFBL9#%$}F7&{m)`#%hG zfB$6|wuIq)9^+$(@iEP?RvRq9`Y+kxob$N8{&Ore*boLYp|Am-F!#DTrbP}$N^xG> zC^yLc{Z~w3OBgPOLIf@nTYz~i?T;Wsgv>m@&{m&_GYG@hkeQ8SqOjAD{z zFzp$GNt#|3cv$kXcgcDawSI>0`rs$7$h~J;NKWg==@@B?-NXXLv%d@|4jkS;ixvOcBA@7?aks z5IBsNp;Q7M-?X^(c%f4kYmySdr($i#bt2YR?3{X2l~Qo0Vg&)FX6&b< z#x`6u;h3=#r|UpdxJo39`GvbkK87%u8j zl3Ir1E_&K1HG;oqF~X{)!y?ZZeR*j8j~jzIop#)~t-JKRB>BT-{sAklisZW6RZ^4O zHQqe%7jr(Wa~(%q7nj#yq&gk-%F!Qm95Ks3id*;Xlg6f2o}p>* zp+z@oh_ng=LD%7?oE4@Wv0pQ1$=C3cnL^+Zh;+3P(IA>`b@~!dR{!;x*LnP<) zB&h@sndTAbWRk%e%ub#%_UT$QMw0f!rTs~H)WKQ9hOt;#&Ur{bmpkGbKWtQ~(VOS{ ztEI+WT2guA49Lwu6zNU_iG(7-uhzepFUVft%Q!*Q=l4Lf8=4Gl9TPpzV|ze*E7}fqq$21Joq}lV`Zbt&G&1?Kk*AGK{X^1F zLU3nEs|K@mr;S5-V(y(bHV?>uO_J`zrJWO@J789I&FIYr{$ccG^X?fv+0@HMXEx&x zW5e-@bKwcjPM)5{XglxLLH@W(&+^f(IQa8xUPlV-&{)D=Hcl*COXMQ;LzU z;Q}`BxDmVX*IfR6&KRF}to)y!F@`qhl_C~}r{&H#52@&FOut3r6M3F;vl_EIXN;}2 z+8l{O^J}6?<_@6-vmwNw2U&p|pI=LC=$IZP2N?WionPchwZB84T=1SG)&BrpWwP&x z{X&dVUBgPrRG}7CS6JjmqcEcsc4>uVPG{_+vqsfl`cRVU;R*FF8L7c++gYRcvzJ`@ zV5#6Ev}^gemtGKwa>RaSSu(yQt`6K4B_4_TcbqGXWuisYE?=HOnC9iV+YeWZuN1i+ zRTwUt>^WkCVqk;+G&a)O;w;3zxb{Rb#&O;Qq+z~ta(LxcW15E=a!8s37(wtJ{NUdaXjd@;`r03vb(Y$eM zj@U22D7kfLAi4b@st`{!??D)PyGyq2C9qIWbSE){Sv`t0aGBQi6giTQi z19>H6FP<~HtK4%*b?sYv&KPXsEHpjXG|Fh^N#A+S*qqaw=R}rxmkHVxRBNXS6w2|t zYmpV7NF{hI*@*sQ!DSJ7M_ir3-u#> z3JlVV-$QSd`jn|BU?AOaitJhY1 zV)@f@^jbIKunA6fEoF&UjFG%R7G5!~;I#hV*zjlA-^MO}OU;k-{^D}U1DAbWDPWv!ncdjSNvr)Oh!tCY(HO-3EjKpcW?iov zmv+|^(xcEIjfLy`uc}dl$w@LTUsw(~|G_UBDwn^5kV z*BF=k2B|gNHN=Ngjz5Rk-wFJ8qD=o)EPv6Lb_D zr@WSN$|@?1zD;y(*OS^-7$_a zX!WEFxy7zO5#7c+{}{(;+(eA5u`YK7_tm?`r@R6kxGx%tb^jVSb3Com_&KLf?->s` z%7GiQ2mcyhs-@&(YxTg`Km;3$1hS$>#t^PE>7nru=$h81S#0AYV|&iK@W^d&?)-g~ zz23?c$~N9X6VZ=Hw)bP>A}x7Z##H=kY{WLIrssHykn$C4wwl_q&y1$Y)p7m9>Lgu) z>sQG_O{SU_YW@Nz>2J8yO7^+Q6kM$zxX?-34Y#OPoF0N_TI3| za-$cU>SBsU@@!wZnC2ULB^EeI)8LXlO}IhgN>4XR|NnWmX?0A)Yg+P*tw=Lm%Liz< zer12TnS!g6H}4lGX(?R&2dd4(-IO6$=hW$wPSQoV7anM}pxTG|l#|pDZuA3{?eZ`! zslJGpopX}jg5wwMTjy+}JWWQW+S+VP5YK^oU0AE;KLF3eUy;76#g7AT2HgHyp>Kiy zKDadqy-koQnE9Da$ExG@5#~H^gu4i7mD$vc*PG_vCjZ8IqqGK-prbo0(j#OA|Hb2c zyujR?H=E~e>f4TAQL#uyy}Ghgx1Fn2l)Tj)(NB@yfLr%a_+F3m18}y7qA9QEqm?D= z?_(OpgZ|dX)RmX*O&^oDc2yevOued;eyfWjHH}fE-VdL>%w}NXbrzht7Jnpkr^AgE z{Aj%*eh=NPu+@fQrB}pyuCnCX&RAU$--O@y;O0G4Ssi~)7lhXEpS_|NHK7oAowlFG?3?7aImS9Xag{w&AJ4ev`6_@gH54a`d+YUc(wQW zD^aW9`oX)Yq3H-$_@kjveA>`-f#VAyrXMtOA-FrUST|EgHnI_VBR3kEe7M{gYSQ|F z?LtlOaC{)t)UG;!kM}E51>8ae;L+IByD6_4X&CgY=kSE9=gMbAiWIq1ky0P2Hfv*3 zhM~HxczQvRye=xz`;SD$sVO4f@iX#bO4F@$hF&V%_Xx6mxGAnWF0NMzbKs=3G)y9TwuR%ZmSEzp@Wlx|D@`5|{oZFR}ewn2R_2 zO8>dLNjaAL5 ztmJ2nRXl-?S{u<+HCpg^Qj&*Zq2?Mj<%DPQiUl>X&f84 zGEe=w$FfOE=^E{}}SEXDFrd=s6p> zpxT>Sw@|hEx?8UxrSu~*|3C>^d{?vw^ORDZP^yFtdt0b}6(`wVqpDjwZ6WXW!(34KEo_>mQQ6ndTA-93qNFd|zlBXZHDwK8x}#C;EUPq^&keWJMdeu5{q>TmInzmU zsf#MJtcA|DQg!`W*7b*#(p}V+BGuEXs(;;(p2q2+0c_~0Rh=(;I~ruVRBB$$T$;-XSogC>3uG!1U8YHu&vUA$|l% zoz>>^RJX2;YRcCm$YH)?P zS9CW1={u@S^)8rK9~{JI)UoJ){U`3Ch=2dy@UX$>y!^a#*@iEF{=JR!eLC$cahI^R z$x_Fc_~qN~Vjc2s-+5MT zT=}wGr-;ucE{nT2;$!`P|D~3#u*f}$@6zQGmnIkYr2c0e)&E&X^?%k;mHvNQM|H@T zbyEMoUq?0hW@nXSd6T-9c^d2Dy2z>~x9qLzDYMdj{)UBBhtHImrq<>%64OW3OwM{# zWlO0l#KKYkd*`f(_4)E04^x(dxrL=w=g)a&HQ|dM%*$HqkO7~UxbphpGrKV>i7n1z zp;Zf$y05BMg)arEO2JH1&zO%(-qu(3kHR?Q^B#P78zQa+|8Hsh^3VNCzLsC|{qI=& zzuOS&lidCXDpIz?6zZ*N1m1b8>VevW%FzBZh7Ky*7Wx_6K1Xi*yu;eDIH82=C;nQGyE=F3sxG24~<}MRn;;T@8C=hReS5P=!tlTda zwvWM!MeT!8JU#?g;_8@$x3b};eROTE8w6rub35+ii5=ibzO-e@4N^UP#y7|u@LB3_ zH%`S3)R=}GoOr=Tj(lo|7wqRmA@E-CPGi#H^We~?+zr5I!MV=diMnB=M3`31xO2n? z9sCCs8i2I;5GdzWjXPcg zPaz+C7F2VO*AM^w{atb2H`|NC5ij_10ENSUfB#n8*KIkBnj>Ct0Ll|-h8ZPkD}f>c z-|o+vm)*DBJCc%eLsig8PrKqZa3Rv+b)$HE&}e?Si#Na{NRR(^kJeoJ{{F4FkGm2@ zkw$PkiZ>Dv<~B;f=Rt9&HUlsC?_FKNljG=^#0zF43+|%|K0tQ(BACSMLTCKV9RI%v0$QG-{*o#BjWd?NH*%u95<7rYZe8{_j};WC;PUj*ka zr?&7RM$Eevtl`ORS81gr?L`jw@9!Cld&d)2(b0$(T!?(};(fry)wCsEyf4^nqK5F| zeL;h0Ns7RW_ZJ)gU@j5y@ZbByf|rmniw(lOLHYP^_kGRF?guablQ!p`tl%!>g#Z3t zuYW8xv7T!I@q%5DKi&&A-N5{T|NdUCxW}8ikpdA9jgmtgJzpe(+fW8R5!!8~%|txh zwvC=I;-M~wrs3|b;DsBc!)L+hT$%0(;Z2F$F$7^6pDF?={ExrJH)Rajj>?;_A?1&ePKn_L%!dyjpB+7wfY&bi-A1p=jctLe7nUsSU?1c*O zo^Sz*#mB>=C>oyyYui$bbo*nb6qCj`kqnB!~8qtwRBWQzkc)=#f1MdvGBMsgI`XE<44^xoNpeTG6 zbaIkOX?P7B=|ubM2}BThiNf*4uzzEj6oU7Hqme(}56(med=OlOg76V=HS)qo!;Vcj zINl5DQ5N0+C1;tGi?@PKC=ahOVq8!@0Uhju3h?Q$UNg=fuZDW${+t8D6y%K;Y|@;X zDdOy50P-YW@D8#j-l~O6nufB74}{lH0X_$|*UIdE=knUsJJY0dbzXd`1v zB%`)6sU>pe>l5y94ASEL;7#Nv;$3CZEaZs~hDVSOJ`I*@M+e4R!qzC0JYC@^l#Ta; zOHeL85*|SLkD32dG51j+8>IG>4oPGb0$X)pjN-L078R35u$3F*Uc|#cksUr3mY^WK zrX$sk{74@Lvyct7B{;*A5%H9!=}WU3`4hknsjy#v&g>Z-2PSyY!|{UC1~7dV(thyJ z59~~QD%|5uZIMwDd_9DIfiHp8hcOB`m<@D68Kl?3>AvJonm|}#EFBzgw8Zp4i3B|0 zka3h6?+bMkI5341Tr`mmNFj3I3zSBt#qdf1XGi*M_yoD)3t`nM?2osGwgzesZwGTx zBKr!ypDN~mGA*4-Nq=Sz5G93uQ5K#B)}>9zkF#NMbm=wr^4fVXeMUHZ1@cMGt(8p3VSIWUJLu95WE+hg$(#$xCZI*G4Rn|+TVvjApvC) z6As=2oGu1!tvR#!hWh3&*KTC!6+IZ0qZAIi!XTr z0yB^uKEsHql|tLRpxWVNl#VwX;JKG5jh)lsb(Dh7f$t8|d-0Kns1=kY(mTWCir20P4sQ)-p;FQZ!!cJFHN=O&xmT(7H+VSjItRdq zLH8Wa7%!;2!7PZkfH#c%@%V@75bn51A@GSX_Almnyx^{U+E&EFlMgr>d}$$L6q zDeWIYqL5Om2Swop+kd3zlc^iDm+>_~c5r|PksUr2)-^Me^6WnWL2jZ*Pp z3eux+@=1knkrp4wo%$H0Vc%G#(M)dF zLt1n0A;Q6yW;`#Kdy4P|a>wVwQ5DT3XS^RY=PN#Tcni1$x#1&WmCDpg1(jqC_aFuF zNzmBZiZ8VhaD|6a5{c5_=qi*{Nv7}-vLsC*Y|IzkBFk})5l%p^_yD*S72p%#BjkrK zgpK*aT&hS9Cm>6a9!8=3cg+97q#`Gos*L&g#82Wq8;Pws%Tgv2s71aXsTD!w`hjT} z-bXpqRso#M7tizX2KX8k;7j0~8fH=vJ_IUj(oA>@xTGfSpG6>&zzMz|>-mN(4mTk>g zriVZh8!}*+EA7k%L0vmD$%FVv*sDEfPrN7OVa-x1@fPp|aw9$+8r{tJkr=fC7j>lC z@e!~`C;A7q>j4#=X&Ss>8cH!YswD9o_B%+;4t8D4q-!XTMAlvD8%QGFvKyTSr4e5Q zx4H9k0^$?kxbAEt-XA*jpwr<6^H3CC(~~AfR-`Y4n?1;v_;~1N>_yKc;0M*c>G}8& z*uD?#$Cz)okL_$Zi+Qkfi5;4M`6jtd6-guIyzrRio;0~E+axm}22F zR6;(Ha4+&=hE0Okk<)u#DM90V{0;LH-> zMHEj)S@0D~Bfc2coJpnDD`*;O&Kf2oqn>I> z!31Y%v02ErmxD&Z@utYfc1Si44eBehE!oh-0 znn}k|Df?QV;_$*Kbf=v~dpf&1I6IK!R`Xa3P^UB{29d{eS`q-us($6eu6+AJBXhNbpnv zvnmD3fSaB${3uX7T=R@{6e#8yWA&F8WJZaCVUJ=u8U^xzPXACv6iD#pTdI@-IlZG` zsF*6vgPQjgh=WDLN63Rry-PV5aupr+6K_J0hC;_la;d9KE_skX1-h8=GX^6i(qhh{ zNOsPI-Q{vAfdYBJW5|sHrNf~LxfDjGzVIqa5;X)@n#-jOYA6auSjZ)J4kp;Myj%*T zhCHC0^-{v9Aq6b0C?^l5d}vIrESEB=q7-PVBA58h5YL<>^J;P_l>!N_tS*v2EGdzovOWij* z3M4slmW?<83KRmJ8p|bb3M9CtiIGexQG64*)X14LqKcegRx^GKLxBYQw~$LY6vzu+ z)lhvDC(*0`MuAqf9aih8K}98D+r+eW@+-aezOgZ1O4TN7Hz50@RQd zY&d{XzzI7+-9S$GlhM4)`20XXoDDqeEti7WAq`#~M4w=X99T4%15+!47Cv~=>tO^c z;ANKJZe&B@lHd)bp;mICY=~U4qd*GS4aJhbJDhLiPYMSJgXyS*0|+XH;yHj|Q4r+$Wr99FLx{tt<-WwW`r|9i)I|?9u0=$lNqVq!eNNS7pMg`_i4Zx3g-(Sq5#gm z08amz+AVvHDVILs1I6oqDM&6YM$Y&MSo0TVL%a>#h_v`P_;eQi0AB=q1vC8czVIjt z!)L)xbLb2B2)G3)EIE644q4)PNWJuAE@#h4@)&x_IE+8mZ19B}Q9c_|;alWRd?|DY zp+I;qcnvw=^Pu}YdNp1T-=PeV9_EJfGeW$e-F(tlq{G6lD1~@;7=Y3&ng4}}LWyk9 zgvq5lC>mb?^}o_f@xky0ip0k)kV|zJG8^L6a1si_2f~}kfX{`s!tvt3&>i{X1K>5} zgD-&3f8*@D2o(J$m+CK~5O@c;8O7l91s79ld;+W*K@YCP%V#(XIpZVX7No$(!N5h&cs9n4tyo8KJ1oAK)qPUvjec?=$fe(U?tLVvi zC)fkU<2~TdC{d(=M^OsC7}i=%q49oj57JfUH3ZB?PI$p$WQB)DsfCH*L_h;4AvHb# zZblk>JiLc&@cHncXwDK}0xSH%nc*$rLli9h8VVZ2bph`Q7p$eT;ltq`v|Uec#%o~X4b%|7Y1P2S8@aNPsRr7|Q7a-I z+Hc}4MS3XTOo8xL@NeXWFM{`sTj)6KPy`>vGt&tV?`@^ykZBQow2g^K#KU{rDYfwM zUIKH2@bKOas#v6lk9JZ}5f9hwqHy?F*e8*~k>>j!;2OoRbALyA5((Zwfq20Z6pj~E z?q($5t>Av7;0)5>xILsJQv#^fuoU^@1@#A*1Mq@tQ4~HK4n9Pm5b-b?X{yrm;SuD7 zcS>dUMA@YA7BgM-!_3!gaD&59uGkp{qEx(~<_NtOFE|So;=|!hl#b7ZmPZ+ic){@~ z6Q2kRQ4-!RO)f1z#zl)`MM32WMvakx1Lp5^cEIPuuv1hiJ{|srqVRd}Ju1ML!kVY4 zp=!Jygny$Lu`m3DitxPemTZv?Y3yK26hXWzT!q5$(a>1;EIpHe8g4kpl!;G;a)d^XgbXBx%}Zb4yqn+r_U7daWc8n((}cv~~k!5Jv68uNb; z<^akhQ6^k_iTQv;v2eg;-Y(&j;6EsuG$pXZ6?!t>4Gu<;cpo?o#p8pa30bjkBFsh} z#0#2bGyL!h*eKh`#ejelfx$?RkAWwUKRz8kK?c0lRk_p@*@!cMKOqhA2Dlx$;uBy# z(&39?*K3?H-W{$$PWV*#4@x1Q5+kP7b#@Y&!l5VAdmjY9B(?@$W96b{RyRzwR8xw=g`S?h91Sv?92A`og;)~4q zA%9)DLP{c_hP_cC-V@G3#rR;j4W;7~;7yc`&xPd`{PLPiEnyoJOuQ=`hr;mwa5Zuu zO*A}(+=(1ZCpWAm2Y?DFZSpgf&zwX@IwZU6C7!+~E}Di8sJMkS{(4 zo#~HYADk@U$)MA4DLZ zfE~|pbQg)>pgM%{KCmIrZq(wPp(nDy3wEhT4dLBkJ2kZ;;^7k%fG>ne^%YV$J{4X^ zA^05F*`Afp@H!(#kNgPu!A+=ynuv$N4OqhhFWA6AA?4v6U=b?73pQ@3kkatZupi36 z2f|IrgPMznH;@-T#Ze)-HX#jO(5WfyA5B1*cg_ka1YZjGH={#|9blan3?aN4j@8gt zZ0S(2q6;-gycN_T172`2&v{J1>!FE9I{M&~U<;Np@n&BQ`~i95ecLkr4-!xl$cFEb z0dLK+CaqBt-U|k{<6!tg*s?umfY-uOl!_N@-GSP|d&3ndp)S20USKH^MLni-*ooyr zf{E9`c_^nY^M4p-2hxxz5ne;Sb!aE}5e4F>DyKa_^|hx?Hy-q4xlU63~i z$b=731n~t>uA_Gp8$JiV?#ikb_!9V2 zH!@{k1KfoI@r6+3&OW5EfDKRp-T`(-ad;g(j&i6iV;1Hk(z3y-JCh8Gv!l&m?VcPA zZwG%x1?&(Gdw4Lrk=_FyL8Zi}!H%pQ6G(a;yoapt`S4F3Oj$&lSXj`XK0&--UoYA} zih!4wLVAI`@Wt>BPo^x!=Rw0DGU8x@$9-sHu`{$AB8DS{gSNxy&BDVmNR9V{Pf-#3 z7Qugf8M?%mz=gvpH1XlXjcg#0L7)Wsjo{*ekA!K+hdz-D&y1u>@tH73&n$))tTu|_ zi?@c}C>$?%24&z&;f&EtO7>ii;W}i8SC3Ih(~-52Kn&&)vczlrSwLq3y}CY=3ACTY z8Q>kDeE>}(JRCNeixA!yUPLkY9OyWOro=nJnaCX<1WzG1dM847IB!(%8IpAM_fpi1#J@D~(?4~Caf96lSm&!nmFg6f|cL3lv}%Ek*` zM0xlu*d>S>73tx=AlhF+RiqFoK{j~7roWIG-WkqDPWWJW1bO4rV6#~aUAzX4K>m0= zT#AD5k?`?s3W6_$e!&VU1TVM>Wl_7)M$CvgoTb0`=AKCKb()E@KI15LKWi$d!Pio9-cvQ_*^)79_=4Pz<|j`c@5YAGZrwX;{}H= zq~v%#e1LycG;tOy|W%!r2kzN&dlb2&A z*V8}n;qc=I`T=RAjlA5Bqvzuz;Hu5k96lP3-bT%k-VZ+6P66@8Ld=XERC^;P3b-JV zvlN-a277qfM7-NxIw|tR>tQHLB-1ds8TsSm;e8a$z6G#h62lDd3Wp;ndp)jV(@v;>L`5+FW3kL;r-!)H1anR2*-3iPLFls%-}>6jE{z{>5K|| z7?hnPGrR&eMFs5Z3{#N}@q%klQ*-!OIN}Tk!0VySS(*}W2ZK=-`wAMP`IA8)9^ONF z_znCoWNqLOXy9^<`H#8ur z3C#f&_jo~p7t|ped<5K!LhxzuIdZ@k3I0uC@q%-ZH$L2mIgfms(z)Opu>tR&&#PhN zgm=Bq^oeZn3GfP1<8z?-13DcUIl=ZQ67L54p=i7pJXpZl<5S`4$DA-e3;u(Q;bau{ zgmyuZ_)^%Yklu^;g1@0Sd;~Om#)Sm0fP;$I2k!&-zo2QHnR;Q}my8tR)o=vL#Oq-i z%Eo8FC9fEw_{3L?|8oRF*f|qELAER`9AsisF z&=fuZDrHK^x*2mj9Al>B9TXD|++U6O7W4!79C?$b6t=ZiN)qv|(9_5tO9Ea{cwZ8I zTT+e5ho#oMYAL1M=A?nYqau73Y|Bz_ex(1lR2yd|mVW!PtefDUb(E3^=>>NoJznrE zGT@EE6rd0S-!D}6{epC2VY(G4mxBqWq9VNDZ6t|(VYGu%ip9smM<|Ij-!GCU7SGc; zvh=(lg}^A}h8OJ9p0me$LW>TZ4PLMi z1>*&?-IP)UUa)URY8USXRh^WQyGY;3$Vzquya)(3=*;SNcrEOYGVnfdI?BQa!nr!7 zl!gz1d%CljG`;|O_fSg3c)O&DkW3FQ(m00*cYB0KuzG?1}dd9KXCT=OgPn><^EhKH0(Br<|E!+ z5Lpm!Iha28#)tN2LkYY$gdWR=e7IvM9S5HXs|{o6a=bNMjQsHtu%RzC!M;v#@d#=H z9{~eLa)6c`7+ygvHz#Go*T{+Z5_nRtI2v(R#jkcnoup9EmyTeBljkFnw z3JJKe9-svq1oM#%-ZDTbokO{#DTXbla4@_UK0_Je0C4P2)DYeuPGBWMJw63?o~o3> z$wvpZ(@uM0h9o9u?tB;U7!rux(U4$^NBeJ4EK5!DUzz4u(D2+6ca3e~= z$HCL62%oW^@qddz0fAij3B}^2WM(}SjZcJcgeTJySnmL7!~x(Ql!8x!ibEU>uY;3N zu}A}}rP6uv)^O@!<`R4ue2sL*c4UgFdxWaSyTW-$gO7q$k5YYjYq$?t;Zxv@G@1+_ z3Adwod;&B(MnAx7Vcp~6ipi*f-B2Pv0MpQtFpUM{dvjuQ|`v zONb2_a6uMlfDeaHQ7pa)Zo9-;;uGMN%k&9+HdJL(P`nN%q7r--EJB(N>D>ueBe0z^EjW30cf78S9 zPH-^FC%q4J&ZpXm7wmB;mzV9;QC$Wbl#jE^@-#y2gb~lnu;TZ;g5p^f`TROru<{$Kiyw*#if2j$ z;|2F44}N4SC?1UAg%=bL#jqrepm>&rHC`|rW#I+4qa3{8DI5HHA5_L4R6g74qb{_eJ_vgPZmzvD8B7ZgjsXYpCRU@zpvC!&I5P#p1sEc-5% z;swP5_FkkBOh!68RT)_UUh=>TK0+a+5oAGk$%79jRmq1X+|?D&S3|AS+i(?!1l@WO-{TjTe?>#E0Wqr&=sY%}+0xa3HHt zOKM(<39{z2hyr5WaIhv_d@Hp~ge3vb+ zj4yIC-Q_W=JVAAUac5K3^+DIs7LuBJ%|s#P-W79vTMY?dg} zSeeXst47My$>VmbTF7J?)9O7?XF9bb+NWNTyFVPf`t&$qdP(_o-URWFe;2 z`&CXM520 z*zFnH*}_uMeA_|NgbpY+FV|#&$AW(rA6DS#Sw1?UN73^@^|ma#ivd4 zo~f!Cl|k=wo>}&t-Nq-qa^li0-P#xX`>(s2xUIp8tO2vHwf9Z(n%{iPv3d0h=idIs z*x}bpKa^Y=a@Av1?w!yf4Gx?ed(YOlVbHt_KgF##%(~fb(fVc!en}qlWABX}_jOHQ zpnB2hkGF=6vv<5-RJ=|#>11cep*@_}=M?_Xa$na*&u_jesc_$TWov_Eo6-|qcW<_s zQu()sH64TJU9w-&_*lQOulqfEa`J_%Ppd&=V>3Ih?*7oHi`FW>DCEPaw4b9J%(2h8 zm4APth0FV`WvPLbTKHn~wq+F> zzDd9J>fn2~KAPeUMIA!B9^YJ{agx>W&`&+brx#mh#2j6UJCU{4> z{qfGM+V1<+hyN9s@3-gv+!@-x6+@d%o}9aI^6DpJey6I3ko}}v(Jg8%4{5h3HQH)P?;5rx zNxA3pis#j6=hZP_j+M)SA0MbkwT_<@dM(PO`Jyw|pPOC1lDy!2(T!`CCv4jd^tf#8 z*7a?S@9kEaqrS6NcRb|U`mEz}mH)CaqbgRaI&Vfnvwxmv3s$Gu}emnki&1 zp>2t)J>zQ?ik@D398sf9CE2QhYLB5WYAnB3zw)N2dw*|!mRL8j{D}t}Dn&k9Q|(6j zvdy~f$rG=t#+0uRp$V-V{;l1{ni6lRDw{m>R4dhHJ3oCaJ^c8`N*`xGT9}qn&`Ex^ zzP@B_v%bFNCe9c$zwYOzsnoRdsa36H%icuh?M+-?YtQ1{of@=Q_twAs>+;iNZ%&$m z+9Vv>J9MlvBh_i?z@P^Yw<;>@N^-gt=enegbde2p8+9)uXw;sUx4h%0H6DL_kbd;w z^wsh8R(yKDd)L{-W^KIQjE`=eHKWxki_24<_TRkR=3-_A`{mVFp4oo=?)KZ$S{i?7 zQzJLzteM$mzoK3RW0tuN+P*o<*I?4t`SIxD&YtCtwcF7nbi|q3Q;$@C>-kr&Ps`Bhq)uXfToCXh?q{mb)Ju_-mQTDBsEoufX zjGa{5HCi{lMnm(-E@QQmoEIFke~@^t*HCjJaReBQ&B^l z^M5o?#X3aQGIhD9s&4(Wd4(@iR5>e|BGz9qPUB~qcu!ToYsdI<>MN%vb$qdN-jEK< zT(@tV^8Vq-#OK{&t)=CsbFXZ5G-;=g{lwDG|H+PU$i9Ueu; z^&T{C=VM*p7Bbt+a?3`_KDYLvwX^(p|Ek?o>-Cz9{4Pi8>Yn*OShIZDy%_tsgOA<# zWvP_V_iy`iiyUKp2K022au@kNuC~ML{6h7xlBMh1Di~Ki{art-pzGSE36nIh^h;{I zwm*O1ZlTl20Z`UPWT{CR_y%|G;p1wcsm~-ajvD7BU zx!vs7?>IapG_0rDj5(jW_DlN^KS}-Y{o(W?+tLv|c366Csc}TNa#QBs6We;vsULA= zLaaioO?wjI^GoASAyz@#TPDVBTU=%1{I)#?9~)F$vhmv7%HDru>@v12u>7d{bl}5} z1t$`(`NnMeYsZuc_Aaj4P}h3Zes*pAs8)x(m;jf4wN5wIgxc--k5;wYNYtNYgU6S&wO>ri>ZuQbS|?eG*wUrRRizfUz!hG`6CAl`XBkrcIbRDsZg2|FrR= zrc9Vs_E38lYfV+LSy`=uYpYi3HZ9wzTe`L}xu>Z@jT@$K9ix1_c76N4j)Q+2cleKq zFK%_{pOW~)+{Kl*8E08ow|MO`W!Iidp%pI4T1|7l*~i|s{+N$<`!^Z4-*Bek?=7P) zy%=9|*1Xfr%=uGJd>rvtpT5(wTxV|jeet0B!zcXi*m~WB{O!5}Cn_E7bFcKlLX%6) zk$qO*tXE}%(KTs$>D^6ASGe}=(7AQO#nx@QO`f;CT-w_CZPL5{)5LT7>R}ak zwXd|Rf%mrY&9XJ8I!rioA@+gi8uM-W7tYjt`QyZO4F-33xl;bm9eelpV-C)895-?M zg?4VvSNn&JIC=eYuLkXtbkl0jZ2b7w>us7<-)7;ss(#Y31D#H34;`r6vEK3|r+Ypw zM*ov$yl#?h43(8?LS-eIdQ?=M1_~QZjS%Z)1#^ogx11;`8=N+nCbp0KH0g1+rjAHZ zNx=oP+psQ4NA%-e%My7BsO^sj`sA+H2I7b$bkK z=Fw%4_Yck7yEp4GuuH#g-JAVKgUE7Jj#X!i4C*NYHPbY0V_V0zTHs=Oaa&c3{wBKH zm)<5iTC?#k&Bsg&_?oKdf~uD0zfILgWUE%VYr1OMnwQfr@Q^L|5Fj@*9X+mUW}`A! zj-O{KlWXSwTV$C`F->DIeaulcs_5}=q{eK4nOM5(ZOYHBe6(=cFzi89e4EATD8#>E$-j&kHs(B^p=fHk{vc4?*C|Juyd2A<{q8vR#r+dFbRp5~F21|LnC4aSOcemZN4;PjFYYfnC*wgW`c1@R24-$HQx_R^B$A<5o zn?F3%%&=?4F~^s2TRObCRd}P(!U_$q9NJK|boGgU++HuA_hD*-RUL1P8r{5AIn#=X z2f8L5+C4eBPVba0E-kwyXhO|XnPuX>%rNp7Nq0xK4}D?1I%x0poSwe_^$gSRznEb> zCXDx2cbPtYM%maG6VAY&ehOrwDVt$hYP2q{U#1jhn6}>qzsy?B|3#kv?W|=wdP`N` zxaEg;mG|tc_QLOCSV-y4kk*&>Uq8LkIia?5!i%tk_N}Y7U$Ok)kNN3mUbTq#a7(^6 zCD&wsaKJC4c6Dn}F?mf=FPEL}KAqy$uimn-e}lOv0>(N-4(hc2Y=hvFT_2^-8r^-( zoI7Iz&#fENuq5n{KX!EQzjIE#jB4j*tT}V{?xKw#J2n8!|kIT+gC2v@XKr<_tAvPy-igvsH&ShPOBPJk!y_q4g zQ^40`4Db5w{`+L1vh>d*JXCgwi7XlI{cfBmPsmhlm396$dHt7j+xL^-?oKBr$V-m) zlx0Ud*0wNvKE|)<3DdC$Dr;k{R=q9fwQ757N1HSGXWAdyXWM+%hI_@`y4GBGwBP!^ zcX!`v8i>@8(cKqYdO>e!uzP#0ifUA3Ib$HPH+L@L8 z!*xr~-+evzMdedN22U$^GO^2HyQ@8}2KPEU&b9inqj}2>KQGKZv&7|$W#bL4o-Vn! z@ceJaC7o?QUCpa)S{!_H^85Hxd)kkiG+Va!e9mTVVO81oRX&^2fA}$8|KOon)z||K z{5JGG6g;n?U;QH&pLMnAW;(Q~t@*CLpBy*!9o46>N0*eb7gBy%XR*4%yof~|mYx1R zVq=pj(UV7vI<-1^(ScFTR;E0ur0912^~cRcqqBdXxarujBEPCpAugen7xKz|e%Z^r zkT1in)BnkE`@g^PFpbDlwUL>boGz)_n=a<5to|Rq12C=ouygpes~sBYZ%(@Kp;4|^ z%=GU4Z+98eeXC^!+b4JK4!JrZk=L9l>g!X&s*TO5pFeHvsBT&Bmb|;-u%!Bo!ETo? zE-78ux!0PwO}87@2t4xWP}-$cAxrw@Jdj1#sWzl_QruAMhV~A99-Ka3f9R59&mtap z{M>Kt2-TAh)>&~6vkQz(!tlG~isoNIi zj*c$PCp>n)dO>^mx0ZvaEglhBabaCquJv!rEXvhd=5GG(!z7D-mQ8xMii@k!d_b#J z9%C{mC3Ld3?{nHBWPL5wQm4Sc-PQw-4?FqS?7!NqlpC7<(|&I6UzKN9%wOKo(&h3( z-T`#yrG1zGV()+s-?O#Y7-`mJ4D{!d=$ll=1w{Z4te z*0$KLa2Q#AbMuc=Cr2)IA9~}$;Ozkyh6bIk5?A!+=NI~smoB*vt~q>r&Bk4WGlnj@ zXxY(H8#JcmO`2QpkqPmu(j1;WGgP~gYTvH$K?`r)UQ6xYZ>tU(F>rF8w$#(CUEjH( zn$hQK21R*vH8!gEyjkPr_tWo0G+Z*=!B=@^X2{ZI<1=myzIngNFF(vtXFs{W_eb@G z+nH+y7tcTR?pMXTnltAab2ho?W>0gh^}%Y1cJ-ve${Nk?$tG`~az)YBtnXWWQlqUi zZ%&-rBJ@q@>=iGn9QHD^J!CO_dBbHZ298gV&3YBG`nNR84OLth>^s!wqPo;Hd-bV$ zM|AJ!wLDkFbGoX Date: Mon, 10 Jan 2022 04:01:07 -0500 Subject: [PATCH 018/109] Update copyright years from 2021 to 2022 to reflect the new year (#2015) --- .github/workflows/release.yml | 4 ++-- dist/linux/appimage/build.sh | 2 +- dist/linux/debian/copyright | 6 +++--- dist/linux/debian/rules | 2 +- dist/mac/dmg/build.sh | 2 +- dist/mac/dmg/resources/license.rtf | 4 ++-- dist/win/build.ps1 | 6 +++--- dist/win/resources/license.rtf | 3 +-- src/main/resources/fxml/preferences_about.fxml | 2 +- 9 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92c2ba49f..630d5bbf2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -184,7 +184,7 @@ jobs: --dest appdir --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2021 Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${{ needs.metadata.outputs.semVerStr }}\"" @@ -529,7 +529,7 @@ jobs: --dest installer --name Cryptomator --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2021 Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" --app-version "${{ needs.metadata.outputs.semVerNum }}" --win-menu --win-dir-chooser diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 24d636bb4..335532cba 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -35,7 +35,7 @@ ${JAVA_HOME}/bin/jpackage \ --dest . \ --name Cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --app-version "${VERSION}.${REVISION_NO}" \ diff --git a/dist/linux/debian/copyright b/dist/linux/debian/copyright index ba6980bb8..34be0a4c9 100644 --- a/dist/linux/debian/copyright +++ b/dist/linux/debian/copyright @@ -4,11 +4,11 @@ Upstream-Contact: Cryptomator Source: https://cryptomator.org Files: * -Copyright: 2016-2021 Skymatic GmbH +Copyright: 2016-2022 Skymatic GmbH License: GPL-3+ Files: debian/org.cryptomator.Cryptomator.appdata.xml -Copyright: 2016-2021 Skymatic GmbH +Copyright: 2016-2022 Skymatic GmbH License: FSFAP License: GPL-3+ @@ -36,4 +36,4 @@ License: FSFAP Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved. This file is offered as-is, without any - warranty. \ No newline at end of file + warranty. diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index b36819f8b..fc237107e 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -29,7 +29,7 @@ override_dh_auto_build: --dest . \ --name cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dfile.encoding=\"utf-8\"" \ diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index b8d17cbe0..ebec045bc 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -54,7 +54,7 @@ ${JAVA_HOME}/bin/jpackage \ --dest . \ --name Cryptomator \ --vendor "Skymatic GmbH" \ - --copyright "(C) 2016 - 2021 Skymatic GmbH" \ + --copyright "(C) 2016 - 2022 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ --java-options "-Dcryptomator.appVersion=\"${VERSION_NO}\"" \ diff --git a/dist/mac/dmg/resources/license.rtf b/dist/mac/dmg/resources/license.rtf index ae2b7ece4..72730adb8 100644 --- a/dist/mac/dmg/resources/license.rtf +++ b/dist/mac/dmg/resources/license.rtf @@ -10,7 +10,7 @@ \f1\b0 \ \ -\f0\b \'a9 2016 \'96 2021 Skymatic GmbH +\f0\b \'a9 2016 \'96 2022 Skymatic GmbH \f1\b0 \ \ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\ @@ -97,4 +97,4 @@ You should have received a copy of the GNU General Public License along with thi SIL OFL 1.1 License:\ - Font Awesome 5.12.0 ({\field{\*\fldinst{HYPERLINK "https://fontawesome.com/"}}{\fldrslt https://fontawesome.com/}})\ \ -} \ No newline at end of file +} diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 8ed5d9193..eb33c2462 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -47,7 +47,7 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --dest . ` --name Cryptomator ` --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2021 Skymatic GmbH" ` + --copyright "(C) 2016 - 2022 Skymatic GmbH" ` --java-options "-Xss5m" ` --java-options "-Xmx256m" ` --java-options "-Dcryptomator.appVersion=`"$semVerNo`"" ` @@ -78,7 +78,7 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" --dest installer ` --name Cryptomator ` --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2021 Skymatic GmbH" ` + --copyright "(C) 2016 - 2022 Skymatic GmbH" ` --app-version "$semVerNo" ` --win-menu ` --win-dir-chooser ` @@ -87,4 +87,4 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" --win-menu-group Cryptomator ` --resource-dir resources ` --license-file resources/license.rtf ` - --file-associations resources/FAvaultFile.properties \ No newline at end of file + --file-associations resources/FAvaultFile.properties diff --git a/dist/win/resources/license.rtf b/dist/win/resources/license.rtf index 6782bebab..28956ed42 100644 --- a/dist/win/resources/license.rtf +++ b/dist/win/resources/license.rtf @@ -3,7 +3,7 @@ {\*\generator Riched20 10.0.17134}\viewkind4\uc1 \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par \par -\b\'a9 2016 \endash 2021 Skymatic GmbH\b0\par +\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par \par This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par \par @@ -82,4 +82,3 @@ You should have received a copy of the GNU General Public License along with thi \tab SIL OFL 1.1 License:\par \tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par } - \ No newline at end of file diff --git a/src/main/resources/fxml/preferences_about.fxml b/src/main/resources/fxml/preferences_about.fxml index 287e91da3..cfa8ec010 100644 --- a/src/main/resources/fxml/preferences_about.fxml +++ b/src/main/resources/fxml/preferences_about.fxml @@ -22,7 +22,7 @@ - From 3e8690ca119104487b072835218d0a03645b456a Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Wed, 12 Jan 2022 11:01:59 +0100 Subject: [PATCH 019/109] removed unnecessary entitlement in mac build [ci skip] --- dist/mac/Cryptomator.entitlements | 2 -- 1 file changed, 2 deletions(-) diff --git a/dist/mac/Cryptomator.entitlements b/dist/mac/Cryptomator.entitlements index 00f46d649..16890d644 100644 --- a/dist/mac/Cryptomator.entitlements +++ b/dist/mac/Cryptomator.entitlements @@ -2,8 +2,6 @@ - com.apple.security.cs.allow-dyld-environment-variables - com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory From 080ddbbb0196554b96a8829e367d62225447fce7 Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Tue, 18 Jan 2022 02:25:04 +0100 Subject: [PATCH 020/109] Applied changes from code review. --- .../org/cryptomator/common/vaults/MountPointRequirement.java | 2 +- src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java | 2 +- src/main/resources/i18n/strings.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java index b7510e811..cc21df03d 100644 --- a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java +++ b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java @@ -7,7 +7,7 @@ package org.cryptomator.common.vaults; public enum MountPointRequirement { /** - * There must not be a parent folder and the actual Mountpoint must not exist. + * The Mountpoint needs to be a filesystem root and must not exist. */ NO_PARENT_NO_MOUNT_POINT, diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index db7b6f7d9..1227fc9de 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -96,7 +96,7 @@ public class UnlockWorkflow extends Task { showInvalidMountPointScene(); } else if (cause instanceof FileAlreadyExistsException) { if (requirement == MountPointRequirement.NO_PARENT_NO_MOUNT_POINT) { - LOG.error("Unlock failed. Drive Letter already occupied: {}", cause.getMessage()); + LOG.error("Unlock failed. Drive Letter already in use: {}", cause.getMessage()); } else { LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); } diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 241c8e538..36932b877 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -116,7 +116,7 @@ unlock.error.heading=Unable to unlock vault ### Invalid Mount Point unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist. unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing. -unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already occupied. +unlock.error.invalidMountPoint.driveLetterOccupied=Drive Letter "%s" is already in use. # Lock ## Force From 9856792921b5d553c6db195aeaca3e3199c904be Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 19 Jan 2022 17:21:32 +0100 Subject: [PATCH 021/109] replaced UserInteractionLock with CompletableFuture in LockWorkflow --- .../ui/common/UserInteractionLock.java | 61 ------------------- .../ui/lock/LockForcedController.java | 20 +++--- .../org/cryptomator/ui/lock/LockModule.java | 15 ++--- .../org/cryptomator/ui/lock/LockWorkflow.java | 39 +++++++----- 4 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/org/cryptomator/ui/common/UserInteractionLock.java diff --git a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java b/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java deleted file mode 100644 index 12c394533..000000000 --- a/src/main/java/org/cryptomator/ui/common/UserInteractionLock.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.cryptomator.ui.common; - -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ReadOnlyBooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -public class UserInteractionLock> { - - private final Lock lock = new ReentrantLock(); - private final Condition condition = lock.newCondition(); - private final BooleanProperty awaitingInteraction = new SimpleBooleanProperty(); - private final AtomicBoolean interacted = new AtomicBoolean(); - private final AtomicReference state; - - public UserInteractionLock(E initialValue) { - this.state = new AtomicReference<>(initialValue); - } - - public synchronized void reset(E value) { - state.set(value); - interacted.set(false); - } - - public void interacted(E result) { - assert Platform.isFxApplicationThread(); - lock.lock(); - try { - state.set(result); - interacted.set(true); - awaitingInteraction.set(false); - condition.signal(); - } finally { - lock.unlock(); - } - } - - public E awaitInteraction() throws InterruptedException { - assert !Platform.isFxApplicationThread(); - lock.lock(); - try { - Platform.runLater(() -> awaitingInteraction.set(true)); - while (!interacted.get()) { - condition.await(); - } - return state.get(); - } finally { - lock.unlock(); - } - } - - public ReadOnlyBooleanProperty awaitingInteraction() { - return awaitingInteraction; - } - -} diff --git a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java index c3a452acc..0b4a23a18 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java +++ b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java @@ -2,7 +2,6 @@ package org.cryptomator.ui.lock; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.UserInteractionLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +9,8 @@ import javax.inject.Inject; import javafx.fxml.FXML; import javafx.stage.Stage; import javafx.stage.WindowEvent; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; @LockScoped public class LockForcedController implements FxController { @@ -18,40 +19,35 @@ public class LockForcedController implements FxController { private final Stage window; private final Vault vault; - private final UserInteractionLock forceLockDecisionLock; + private final AtomicReference> forceRetryDecision; @Inject - public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock) { + public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, AtomicReference> forceRetryDecision) { this.window = window; this.vault = vault; - this.forceLockDecisionLock = forceLockDecisionLock; + this.forceRetryDecision = forceRetryDecision; this.window.setOnHiding(this::windowClosed); } @FXML public void cancel() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL); window.close(); } @FXML public void retry() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.RETRY); + forceRetryDecision.get().complete(false); window.close(); } @FXML public void force() { - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.FORCE); + forceRetryDecision.get().complete(true); window.close(); } private void windowClosed(WindowEvent windowEvent) { - // if not already interacted, set the decision to CANCEL - if (forceLockDecisionLock.awaitingInteraction().get()) { - LOG.debug("Lock canceled in force-lock-phase by user."); - forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL); - } + forceRetryDecision.get().cancel(true); } // ----- Getter & Setter ----- diff --git a/src/main/java/org/cryptomator/ui/lock/LockModule.java b/src/main/java/org/cryptomator/ui/lock/LockModule.java index d1eb5f189..ddee13dff 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockModule.java +++ b/src/main/java/org/cryptomator/ui/lock/LockModule.java @@ -6,13 +6,12 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.DefaultSceneFactory; -import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlLoaderFactory; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.StageFactory; -import org.cryptomator.ui.common.UserInteractionLock; import javax.inject.Named; import javax.inject.Provider; @@ -22,20 +21,16 @@ import javafx.stage.Stage; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; @Module abstract class LockModule { - enum ForceLockDecision { - CANCEL, - RETRY, - FORCE; - } - @Provides @LockScoped - static UserInteractionLock provideForceLockDecisionLock() { - return new UserInteractionLock<>(null); + static AtomicReference> provideForceRetryDecisionRef() { + return new AtomicReference<>(); } @Provides diff --git a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java index 00b25c507..1e05ceb73 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java @@ -8,7 +8,6 @@ import org.cryptomator.common.vaults.Volume; import org.cryptomator.ui.common.ErrorComponent; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.UserInteractionLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,6 +17,10 @@ import javafx.concurrent.Task; import javafx.scene.Scene; import javafx.stage.Stage; import javafx.stage.Window; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; /** * The sequence of actions performed and checked during lock of a vault. @@ -34,43 +37,48 @@ public class LockWorkflow extends Task { private final Stage lockWindow; private final Vault vault; - private final UserInteractionLock forceLockDecisionLock; + private final AtomicReference> forceRetryDecision; private final Lazy lockForcedScene; private final Lazy lockFailedScene; private final ErrorComponent.Builder errorComponent; @Inject - public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) { + public LockWorkflow(@LockWindow Stage lockWindow, @LockWindow Vault vault, AtomicReference> forceRetryDecision, @FxmlScene(FxmlFile.LOCK_FORCED) Lazy lockForcedScene, @FxmlScene(FxmlFile.LOCK_FAILED) Lazy lockFailedScene, ErrorComponent.Builder errorComponent) { this.lockWindow = lockWindow; this.vault = vault; - this.forceLockDecisionLock = forceLockDecisionLock; + this.forceRetryDecision = forceRetryDecision; this.lockForcedScene = lockForcedScene; this.lockFailedScene = lockFailedScene; this.errorComponent = errorComponent; } @Override - protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException { + protected Void call() throws Volume.VolumeException, InterruptedException, LockNotCompletedException, ExecutionException { lock(false); return null; } - private void lock(boolean forced) throws InterruptedException { + private void lock(boolean forced) throws InterruptedException, ExecutionException { try { vault.lock(forced); } catch (Volume.VolumeException | LockNotCompletedException e) { LOG.info("Locking {} failed (forced: {}).", vault.getDisplayName(), forced, e); - var decision = askUserForAction(); - switch (decision) { - case RETRY -> lock(false); - case FORCE -> lock(true); - case CANCEL -> cancel(false); - } + retryOrCancel(); } } - private LockModule.ForceLockDecision askUserForAction() throws InterruptedException { - forceLockDecisionLock.reset(null); + private void retryOrCancel() throws ExecutionException, InterruptedException { + try { + boolean forced = askWhetherToUseTheForce().get(); + lock(forced); + } catch (CancellationException e) { + cancel(false); + } + } + + private CompletableFuture askWhetherToUseTheForce() { + var decision = new CompletableFuture(); + forceRetryDecision.set(decision); // show forcedLock dialogue ... Platform.runLater(() -> { lockWindow.setScene(lockForcedScene.get()); @@ -83,8 +91,7 @@ public class LockWorkflow extends Task { lockWindow.centerOnScreen(); } }); - // ... and wait for answer - return forceLockDecisionLock.awaitInteraction(); + return decision; } @Override From d52e59d7a44090944e182bbd35ad16fcb01a76f4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 19 Jan 2022 19:08:54 +0100 Subject: [PATCH 022/109] dedup by setting title when setting the scene --- .../masterkeyfile/ChooseMasterkeyFileModule.java | 15 +++------------ .../MasterkeyFileLoadingStrategy.java | 7 ++++++- .../masterkeyfile/PassphraseEntryModule.java | 13 ++----------- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java index baa302170..34eb78443 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -2,16 +2,13 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import dagger.Module; import dagger.Provides; -import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.keyloading.KeyLoading; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.stage.Stage; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -29,21 +26,15 @@ interface ChooseMasterkeyFileModule { @Provides @ChooseMasterkeyFileScoped - static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle, @KeyLoading Stage window, @KeyLoading Vault v) { + static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { // TODO: simplify FxmlLoaderFactory try { var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE.getRessourcePathString()); var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); Parent root = loader.load(); - var scene = sceneFactory.apply(root); - scene.windowProperty().addListener((prop, oldVal, newVal) -> { - if (window.equals(newVal)) { - window.setTitle(String.format(resourceBundle.getString("unlock.chooseMasterkey.title"), v.getDisplayName())); - } - }); - return scene; + return sceneFactory.apply(root); } catch (IOException e) { - throw new UncheckedIOException("Failed to load UnlockScene", e); + throw new UncheckedIOException("Failed to load ChooseMasterkeyScene", e); } } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java index a1d85cb36..b4964f9a0 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/MasterkeyFileLoadingStrategy.java @@ -25,6 +25,7 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; +import java.util.ResourceBundle; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -39,19 +40,21 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { private final PassphraseEntryComponent.Builder passphraseEntry; private final ChooseMasterkeyFileComponent.Builder masterkeyFileChoice; private final KeychainManager keychain; + private final ResourceBundle resourceBundle; private Passphrase passphrase; private boolean savePassphrase; private boolean wrongPassphrase; @Inject - public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain) { + public MasterkeyFileLoadingStrategy(@KeyLoading Vault vault, MasterkeyFileAccess masterkeyFileAccess, @KeyLoading Stage window, @Named("savedPassword") Optional savedPassphrase, PassphraseEntryComponent.Builder passphraseEntry, ChooseMasterkeyFileComponent.Builder masterkeyFileChoice, KeychainManager keychain, ResourceBundle resourceBundle) { this.vault = vault; this.masterkeyFileAccess = masterkeyFileAccess; this.window = window; this.passphraseEntry = passphraseEntry; this.masterkeyFileChoice = masterkeyFileChoice; this.keychain = keychain; + this.resourceBundle = resourceBundle; this.passphrase = savedPassphrase.map(Passphrase::new).orElse(null); this.savePassphrase = savedPassphrase.isPresent(); } @@ -121,6 +124,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { var comp = masterkeyFileChoice.build(); Platform.runLater(() -> { window.setScene(comp.chooseMasterkeyScene()); + window.setTitle(resourceBundle.getString("unlock.chooseMasterkey.title").formatted(vault.getDisplayName())); window.show(); Window owner = window.getOwner(); if (owner != null) { @@ -143,6 +147,7 @@ public class MasterkeyFileLoadingStrategy implements KeyLoadingStrategy { var comp = passphraseEntry.savedPassword(passphrase).build(); Platform.runLater(() -> { window.setScene(comp.passphraseEntryScene()); + window.setTitle(resourceBundle.getString("unlock.title").formatted(vault.getDisplayName())); window.show(); Window owner = window.getOwner(); if (owner != null) { diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java index aaf65e3f8..0d67abd50 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -2,16 +2,13 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import dagger.Module; import dagger.Provides; -import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; -import org.cryptomator.ui.keyloading.KeyLoading; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.stage.Stage; import java.io.IOException; import java.io.UncheckedIOException; import java.util.ResourceBundle; @@ -28,19 +25,13 @@ interface PassphraseEntryModule { @Provides @PassphraseEntryScoped - static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle, @KeyLoading Stage window, @KeyLoading Vault v) { + static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { // TODO: simplify FxmlLoaderFactory try { var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_ENTER_PASSWORD.getRessourcePathString()); var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); Parent root = loader.load(); - var scene = sceneFactory.apply(root); - scene.windowProperty().addListener((prop, oldVal, newVal) -> { - if (window.equals(newVal)) { - window.setTitle(String.format(resourceBundle.getString("unlock.title"), v.getDisplayName())); - } - }); - return scene; + return sceneFactory.apply(root); } catch (IOException e) { throw new UncheckedIOException("Failed to load UnlockScene", e); } From 4d4098e0e0e92f99313ea12dac4a30940e7362e0 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 19 Jan 2022 19:49:33 +0100 Subject: [PATCH 023/109] cleanup --- src/main/java/org/cryptomator/common/Passphrase.java | 12 ++++-------- .../org/cryptomator/ui/common/FxmlLoaderFactory.java | 4 ++++ .../masterkeyfile/ChooseMasterkeyFileModule.java | 11 +---------- .../masterkeyfile/PassphraseEntryModule.java | 11 +---------- .../masterkeyfile/PassphraseEntryResult.java | 2 +- .../cryptomator/ui/lock/LockForcedController.java | 4 ---- 6 files changed, 11 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/cryptomator/common/Passphrase.java b/src/main/java/org/cryptomator/common/Passphrase.java index d57252507..036e87104 100644 --- a/src/main/java/org/cryptomator/common/Passphrase.java +++ b/src/main/java/org/cryptomator/common/Passphrase.java @@ -64,16 +64,12 @@ public class Passphrase implements Destroyable, CharSequence { @Override public int hashCode() { - // TODO: do we really need to a secure hashcode? toString leaks the pw anyway - var md = MessageDigestSupplier.SHA256.get(); - ByteBuffer buf = ByteBuffer.allocate(Character.BYTES * length); + // basically Arrays.hashCode, but only for a certain subarray + int result = 1; for (int i = 0; i < length; i++) { - char c = charAt(i); - buf.putChar(i * Character.BYTES, c); + result = 31 * result + charAt(i); } - buf.flip(); - md.update(buf); - return Arrays.hashCode(md.digest()); + return result; } @Override diff --git a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java index c10054ef4..dcff93aab 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlLoaderFactory.java @@ -22,6 +22,10 @@ public class FxmlLoaderFactory { this.resourceBundle = resourceBundle; } + public static FxmlLoaderFactory forController(T controller, Function sceneFactory, ResourceBundle resourceBundle) { + return new FxmlLoaderFactory(Map.of(controller.getClass(), () -> controller), sceneFactory, resourceBundle); + } + /** * @return A new FXMLLoader instance */ diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java index 34eb78443..c7a084584 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -27,16 +27,7 @@ interface ChooseMasterkeyFileModule { @Provides @ChooseMasterkeyFileScoped static Scene provideChooseMasterkeyScene(ChooseMasterkeyFileController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - // TODO: simplify FxmlLoaderFactory - try { - var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE.getRessourcePathString()); - var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); - Parent root = loader.load(); - return sceneFactory.apply(root); - } catch (IOException e) { - throw new UncheckedIOException("Failed to load ChooseMasterkeyScene", e); - } + return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_SELECT_MASTERKEYFILE); } - } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java index 0d67abd50..ed1feb7ee 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -26,16 +26,7 @@ interface PassphraseEntryModule { @Provides @PassphraseEntryScoped static Scene provideUnlockScene(PassphraseEntryController controller, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - // TODO: simplify FxmlLoaderFactory - try { - var url = FxmlLoaderFactory.class.getResource(FxmlFile.UNLOCK_ENTER_PASSWORD.getRessourcePathString()); - var loader = new FXMLLoader(url, resourceBundle, null, clazz -> controller); - Parent root = loader.load(); - return sceneFactory.apply(root); - } catch (IOException e) { - throw new UncheckedIOException("Failed to load UnlockScene", e); - } + return FxmlLoaderFactory.forController(controller, sceneFactory, resourceBundle).createScene(FxmlFile.UNLOCK_ENTER_PASSWORD); } - } diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java index f26184d9d..19057acca 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryResult.java @@ -2,7 +2,7 @@ package org.cryptomator.ui.keyloading.masterkeyfile; import org.cryptomator.common.Passphrase; -// TODO needs to be public due to Dagger -.- +// TODO: change to package-private, as soon as this works for Dagger -.- public record PassphraseEntryResult(Passphrase passphrase, boolean savePassphrase) { } diff --git a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java index 0b4a23a18..15cf119be 100644 --- a/src/main/java/org/cryptomator/ui/lock/LockForcedController.java +++ b/src/main/java/org/cryptomator/ui/lock/LockForcedController.java @@ -2,8 +2,6 @@ package org.cryptomator.ui.lock; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.common.FxController; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.inject.Inject; import javafx.fxml.FXML; @@ -15,8 +13,6 @@ import java.util.concurrent.atomic.AtomicReference; @LockScoped public class LockForcedController implements FxController { - private static final Logger LOG = LoggerFactory.getLogger(LockForcedController.class); - private final Stage window; private final Vault vault; private final AtomicReference> forceRetryDecision; From e1a72c41a5fcbe87c5f7d328ac456e514a59202b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 19 Jan 2022 20:01:48 +0100 Subject: [PATCH 024/109] remove unused imports --- src/main/java/org/cryptomator/common/Passphrase.java | 3 --- .../keyloading/masterkeyfile/ChooseMasterkeyFileModule.java | 4 ---- .../ui/keyloading/masterkeyfile/PassphraseEntryModule.java | 4 ---- 3 files changed, 11 deletions(-) diff --git a/src/main/java/org/cryptomator/common/Passphrase.java b/src/main/java/org/cryptomator/common/Passphrase.java index 036e87104..cf64c7a10 100644 --- a/src/main/java/org/cryptomator/common/Passphrase.java +++ b/src/main/java/org/cryptomator/common/Passphrase.java @@ -1,9 +1,6 @@ package org.cryptomator.common; -import org.cryptomator.cryptolib.common.MessageDigestSupplier; - import javax.security.auth.Destroyable; -import java.nio.ByteBuffer; import java.util.Arrays; /** diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java index c7a084584..21ae2b26c 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileModule.java @@ -6,11 +6,7 @@ import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; import javafx.scene.Scene; -import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java index ed1feb7ee..2c65d440b 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/PassphraseEntryModule.java @@ -6,11 +6,7 @@ import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlLoaderFactory; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; import javafx.scene.Scene; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.ResourceBundle; import java.util.concurrent.CompletableFuture; From a9846744fe875dd040192ffa2bacf66f817f4ea7 Mon Sep 17 00:00:00 2001 From: KarlKeu00 <47644344+KarlKeu00@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:16:06 +0100 Subject: [PATCH 025/109] Update JavaFX to 17.0.2 (#2031) Updating JavaFX inside Maven to 17.0.2 to fix JDK-8275723. This is only relevant for development on a Mac with an M1 processor. --- .gitignore | 4 +++- pom.xml | 2 +- src/main/resources/license/THIRD-PARTY.txt | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index be67207df..8e239b35e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ pom.xml.versionsBackup .idea/jarRepositories.xml .idea/uiDesigner.xml .idea/**/libraries/ -*.iml \ No newline at end of file +*.iml + +hs_err_pid*.log \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6f0243597..3caa9b899 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 1.2.6 - 17.0.1 + 17.0.2 3.12.0 3.18.2 2.2 diff --git a/src/main/resources/license/THIRD-PARTY.txt b/src/main/resources/license/THIRD-PARTY.txt index ba4310dd5..9c9d92c7d 100644 --- a/src/main/resources/license/THIRD-PARTY.txt +++ b/src/main/resources/license/THIRD-PARTY.txt @@ -62,10 +62,10 @@ Cryptomator uses 40 third-party dependencies under the following licenses: GPLv2: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) GPLv2+CE: - - javafx-base (org.openjfx:javafx-base:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-base/) - - javafx-controls (org.openjfx:javafx-controls:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-controls/) - - javafx-fxml (org.openjfx:javafx-fxml:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-fxml/) - - javafx-graphics (org.openjfx:javafx-graphics:17.0.1 - https://openjdk.java.net/projects/openjfx/javafx-graphics/) + - javafx-base (org.openjfx:javafx-base:17.0.2 - https://openjdk.java.net/projects/openjfx/javafx-base/) + - javafx-controls (org.openjfx:javafx-controls:17.0.2 - https://openjdk.java.net/projects/openjfx/javafx-controls/) + - javafx-fxml (org.openjfx:javafx-fxml:17.0.2 - https://openjdk.java.net/projects/openjfx/javafx-fxml/) + - javafx-graphics (org.openjfx:javafx-graphics:17.0.2 - https://openjdk.java.net/projects/openjfx/javafx-graphics/) LGPL 2.1: - jnr-posix (com.github.jnr:jnr-posix:3.1.10 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix) LGPL-2.1-or-later: From d6ccb410274d925bf764da04d6103921a8817268 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 26 Jan 2022 12:58:58 +0100 Subject: [PATCH 026/109] add modules required for JFR --- .github/workflows/release.yml | 2 +- dist/linux/appimage/build.sh | 2 +- dist/linux/debian/rules | 2 +- dist/mac/dmg/build.sh | 6 +++--- dist/win/build.ps1 | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 630d5bbf2..6fe6283ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,7 +167,7 @@ jobs: --verbose --output runtime --module-path "${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr --no-header-files --no-man-pages --strip-debug diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 335532cba..af1f2291f 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -18,7 +18,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index fc237107e..b381a2331 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -15,7 +15,7 @@ override_dh_auto_clean: override_dh_auto_build: jlink \ --output runtime \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index ebec045bc..c90411acb 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -22,8 +22,8 @@ VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q # check preconditions if [ -z "${JAVA_HOME}" ]; then echo "JAVA_HOME not set. Run using JAVA_HOME=/path/to/jdk ./build.sh"; exit 1; fi -command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } -command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found."; exit 1; } +command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found. Fix by 'brew install maven'."; exit 1; } +command -v create-dmg >/dev/null 2>&1 || { echo >&2 "create-dmg not found. Fix by 'brew install create-dmg'."; exit 1; } if [ -n "${CODESIGN_IDENTITY}" ]; then command -v codesign >/dev/null 2>&1 || { echo >&2 "codesign not found. Fix by 'xcode-select --install'."; exit 1; } if [[ ! `security find-identity -v -p codesigning | grep -w "${CODESIGN_IDENTITY}"` ]]; then echo "Given codesign identity is invalid."; exit 1; fi @@ -37,7 +37,7 @@ cp ../../../target/cryptomator-*.jar ../../../target/mods ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JAVA_HOME}/jmods" \ - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility \ + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr \ --no-header-files \ --no-man-pages \ --strip-debug \ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index eb33c2462..2919e493a 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -30,7 +30,7 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --verbose ` --output runtime ` --module-path "$Env:JAVA_HOME/jmods" ` - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility ` + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr ` --no-header-files ` --no-man-pages ` --strip-debug ` From 1641a06d650ace9fa6a1c6aa7da11804d09b9aec Mon Sep 17 00:00:00 2001 From: JaniruTEC Date: Sun, 30 Jan 2022 01:33:44 +0100 Subject: [PATCH 027/109] Renamed NO_PARENT_NO_MOUNT_POINT to UNUSED_ROOT_DIR in MountPointRequirement --- .../common/mountpoint/CustomMountPointChooser.java | 2 +- .../common/mountpoint/TemporaryMountPointChooser.java | 2 +- src/main/java/org/cryptomator/common/vaults/DokanyVolume.java | 2 +- src/main/java/org/cryptomator/common/vaults/FuseVolume.java | 2 +- .../org/cryptomator/common/vaults/MountPointRequirement.java | 2 +- .../ui/unlock/UnlockInvalidMountPointController.java | 4 ++-- src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index a78564db4..c55ede640 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -56,7 +56,7 @@ class CustomMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } default -> { - //Currently the case for "NO_PARENT_NO_MOUNT_POINT, PARENT_OPT_MOUNT_POINT" + //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } diff --git a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java index b119ff084..bcda3d8f2 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java @@ -65,7 +65,7 @@ class TemporaryMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } default -> { - //Currently the case for "NO_PARENT_NO_MOUNT_POINT, PARENT_OPT_MOUNT_POINT" + //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } diff --git a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java index 64bd41edf..c08642073 100644 --- a/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java @@ -86,7 +86,7 @@ public class DokanyVolume extends AbstractVolume { @Override public MountPointRequirement getMountPointRequirement() { - return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.NO_PARENT_NO_MOUNT_POINT : MountPointRequirement.EMPTY_MOUNT_POINT; + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.EMPTY_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java index e3b0d2ed5..0321cfa0f 100644 --- a/src/main/java/org/cryptomator/common/vaults/FuseVolume.java +++ b/src/main/java/org/cryptomator/common/vaults/FuseVolume.java @@ -126,7 +126,7 @@ public class FuseVolume extends AbstractVolume { if (!SystemUtils.IS_OS_WINDOWS) { return MountPointRequirement.EMPTY_MOUNT_POINT; } - return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.NO_PARENT_NO_MOUNT_POINT : MountPointRequirement.PARENT_NO_MOUNT_POINT; + return this.vaultSettings.getWinDriveLetter().isPresent() ? MountPointRequirement.UNUSED_ROOT_DIR : MountPointRequirement.PARENT_NO_MOUNT_POINT; } public static boolean isSupportedStatic() { diff --git a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java index cc21df03d..deec61e1a 100644 --- a/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java +++ b/src/main/java/org/cryptomator/common/vaults/MountPointRequirement.java @@ -9,7 +9,7 @@ public enum MountPointRequirement { /** * The Mountpoint needs to be a filesystem root and must not exist. */ - NO_PARENT_NO_MOUNT_POINT, + UNUSED_ROOT_DIR, /** * No Mountpoint on the local filesystem required. (e.g. WebDAV) diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java index f84842807..234bac65b 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockInvalidMountPointController.java @@ -42,14 +42,14 @@ public class UnlockInvalidMountPointController implements FxController { } public boolean getDriveLetterOccupied() { - return getMountPointRequirement() == MountPointRequirement.NO_PARENT_NO_MOUNT_POINT; + return getMountPointRequirement() == MountPointRequirement.UNUSED_ROOT_DIR; } private MountPointRequirement getMountPointRequirement() { var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!")).getMountPointRequirement(); assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) - assert requirement != MountPointRequirement.NO_PARENT_NO_MOUNT_POINT || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows + assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows return requirement; } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 1227fc9de..6964c3c86 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -83,7 +83,7 @@ public class UnlockWorkflow extends Task { var requirement = vault.getVolume().orElseThrow(() -> new IllegalStateException("Invalid Mountpoint without a Volume?!", impExc)).getMountPointRequirement(); assert requirement != MountPointRequirement.NONE; //An invalid MountPoint with no required MountPoint doesn't seem sensible assert requirement != MountPointRequirement.PARENT_OPT_MOUNT_POINT; //Not implemented anywhere (yet) - assert requirement != MountPointRequirement.NO_PARENT_NO_MOUNT_POINT || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows + assert requirement != MountPointRequirement.UNUSED_ROOT_DIR || SystemUtils.IS_OS_WINDOWS; //Not implemented anywhere, but on Windows Throwable cause = impExc.getCause(); // TODO: apply https://openjdk.java.net/jeps/8213076 in future JDK versions @@ -95,7 +95,7 @@ public class UnlockWorkflow extends Task { } showInvalidMountPointScene(); } else if (cause instanceof FileAlreadyExistsException) { - if (requirement == MountPointRequirement.NO_PARENT_NO_MOUNT_POINT) { + if (requirement == MountPointRequirement.UNUSED_ROOT_DIR) { LOG.error("Unlock failed. Drive Letter already in use: {}", cause.getMessage()); } else { LOG.error("Unlock failed. Mountpoint already exists: {}", cause.getMessage()); From 7172462b4b4c975c606311a99b0f3aa4ee40c999 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 23 Feb 2022 22:27:41 +0100 Subject: [PATCH 028/109] Create installation bundle with winfsp: * via wix burn engine * licenses generated on the fly * customized theme * local only --- dist/win/.gitignore | 6 +- dist/win/build.ps1 | 74 +++++++++++-- dist/win/bundle/bundleWithWinfsp.wxs | 43 ++++++++ dist/win/bundle/customBootstrapperTheme.wxl | 64 +++++++++++ dist/win/bundle/customBootstrapperTheme.xml | 100 ++++++++++++++++++ dist/win/bundle/resources/Cryptomator.ico | Bin 0 -> 162342 bytes dist/win/bundle/resources/licenseTemplate.ftl | 41 +++++++ dist/win/bundle/resources/logo.png | Bin 0 -> 2659 bytes dist/win/bundle/resources/logoSide.png | Bin 0 -> 37864 bytes dist/win/resources/license.rtf | 84 --------------- dist/win/resources/licenseTemplate.ftl | 37 +++++++ 11 files changed, 354 insertions(+), 95 deletions(-) create mode 100644 dist/win/bundle/bundleWithWinfsp.wxs create mode 100644 dist/win/bundle/customBootstrapperTheme.wxl create mode 100644 dist/win/bundle/customBootstrapperTheme.xml create mode 100644 dist/win/bundle/resources/Cryptomator.ico create mode 100644 dist/win/bundle/resources/licenseTemplate.ftl create mode 100644 dist/win/bundle/resources/logo.png create mode 100644 dist/win/bundle/resources/logoSide.png delete mode 100644 dist/win/resources/license.rtf create mode 100644 dist/win/resources/licenseTemplate.ftl diff --git a/dist/win/.gitignore b/dist/win/.gitignore index 2b66ddbed..9cce929df 100644 --- a/dist/win/.gitignore +++ b/dist/win/.gitignore @@ -1,3 +1,7 @@ runtime Cryptomator -installer \ No newline at end of file +installer +*.wixobj +*.pdb +*.msi +license.rtf \ No newline at end of file diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 2919e493a..565ffd582 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -1,11 +1,14 @@ +# check parameters +$clean = $args[0] -eq "fresh" + # check preconditions -if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) -{ +if ((Get-Command "git" -ErrorAction SilentlyContinue) -eq $null) +{ Write-Host "Unable to find git.exe in your PATH (try: choco install git)" exit 1 } -if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null) -{ +if ((Get-Command "mvn" -ErrorAction SilentlyContinue) -eq $null) +{ Write-Host "Unable to find mvn.cmd in your PATH (try: choco install maven)" exit 1 } @@ -21,11 +24,19 @@ Write-Output "`$revisionNo=$revisionNo" Write-Output "`$buildDir=$buildDir" Write-Output "`$Env:JAVA_HOME=$Env:JAVA_HOME" +$vendor = "Skymatic GmbH" +$copyright = "(C) 2016 - 2022 Skymatic GmbH" + # compile &mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\..\target\mods" # add runtime +$runtimeImagePath = '.\runtime' +if ($clean -and (Test-Path -Path $runtimeImagePath)) { + Remove-Item -Path $runtimeImagePath -Force -Recurse +} + & "$Env:JAVA_HOME\bin\jlink" ` --verbose ` --output runtime ` @@ -36,6 +47,11 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --strip-debug ` --compress=1 +$appPath = '.\Cryptomator' +if ($clean -and (Test-Path -Path $appPath)) { + Remove-Item -Path $appPath -Force -Recurse +} + # create app dir & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` @@ -46,8 +62,8 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator ` --dest . ` --name Cryptomator ` - --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2022 Skymatic GmbH" ` + --vendor $vendor ` + --copyright $copyright ` --java-options "-Xss5m" ` --java-options "-Xmx256m" ` --java-options "-Dcryptomator.appVersion=`"$semVerNo`"" ` @@ -64,11 +80,21 @@ Copy-Item "$buildDir\..\..\target\cryptomator-*.jar" -Destination "$buildDir\..\ --resource-dir resources ` --icon resources/Cryptomator.ico +#Create RTF license for msi +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` + "-Dlicense.thirdPartyFilename=license.rtf" ` + "-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" ` + "-Dlicense.outputDirectory=$buildDir\resources\" + # patch app dir Copy-Item "contrib\*" -Destination "Cryptomator" attrib -r "Cryptomator\Cryptomator.exe" -# create .msi bundle +$aboutUrl="https://cryptomator.org" +$updateUrl="https://cryptomator.org/downloads/" +$helpUrl="https://cryptomator.org/contact/" + +# create .msi $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" & "$Env:JAVA_HOME\bin\jpackage" ` --verbose ` @@ -77,14 +103,42 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" --app-image Cryptomator ` --dest installer ` --name Cryptomator ` - --vendor "Skymatic GmbH" ` - --copyright "(C) 2016 - 2022 Skymatic GmbH" ` + --vendor $vendor ` + --copyright $copyright ` --app-version "$semVerNo" ` --win-menu ` --win-dir-chooser ` --win-shortcut-prompt ` - --win-update-url "https:\\cryptomator.org" ` + --win-update-url $updateUrl ` --win-menu-group Cryptomator ` --resource-dir resources ` + --about-url $aboutUrl ` --license-file resources/license.rtf ` --file-associations resources/FAvaultFile.properties + +#Create RTF license for bundle +#TODO: actually we should patch also the third-party-file in the Cryptomator jar +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` + "-Dlicense.thirdPartyFilename=license.rtf" ` + "-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" ` + "-Dlicense.outputDirectory=$buildDir\bundle\resources\" + +# download Winfsp +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s +$winfspMsiUrl = "https://github.com/billziss-gh/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi" +Write-Output "Downloading ${winfspMsiUrl}..." +Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default + +# copy MSI to bundle resources +Copy-Item ".\installer\Cryptomator-*.msi" -Destination ".\bundle\resources\Cryptomator.msi" + +# create bundle including winfsp +& "$env:WIX\bin\candle.exe" .\bundle\bundleWithWinfsp.wxs -ext WixBalExtension -out bundle\ ` + -dBundleVersion="$semVerNo.$revisionNo" ` + -dBundleVendor="$vendor" ` + -dBundleCopyright="$copyright" ` + -dAboutUrl="$aboutUrl" ` + -dHelpUrl="$helpUrl" ` + -dUpdateUrl="$updateUrl" +& "$env:WIX\bin\light.exe" -b . .\bundle\BundlewithWinfsp.wixobj -ext WixBalExtension -out installer\CryptomatorBundle.exe \ No newline at end of file diff --git a/dist/win/bundle/bundleWithWinfsp.wxs b/dist/win/bundle/bundleWithWinfsp.wxs new file mode 100644 index 000000000..91093df7d --- /dev/null +++ b/dist/win/bundle/bundleWithWinfsp.wxs @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/win/bundle/customBootstrapperTheme.wxl b/dist/win/bundle/customBootstrapperTheme.wxl new file mode 100644 index 000000000..cc464ce9c --- /dev/null +++ b/dist/win/bundle/customBootstrapperTheme.wxl @@ -0,0 +1,64 @@ + + + + + + [WixBundleName] Setup + [WixBundleName] + Welcome + This Setup will install [WixBundleName] and additional dependencies on your computer. + Version [WixBundleVersion] + Are you sure you want to cancel? + Previous version + Setup Help + /install | /repair | /uninstall | /layout [directory] - installs, repairs, uninstalls or + creates a complete local copy of the bundle in directory. Install is the default. + +/passive | /quiet - displays minimal UI with no prompts or displays no UI and + no prompts. By default UI and all prompts are displayed. + +/norestart - suppress any attempts to restart. By default UI will prompt before restart. +/log log.txt - logs to a specific file. By default a log file is created in %TEMP%. + &Close + [WixBundleName] <a href="#">license terms</a>. + I &agree to the license terms and conditions + &Options + &Install + &Close + Setup Options + Install location: + &Browse + &OK + &Cancel + Setup Progress + Processing: + Initializing... + &Cancel + Modify Setup + &Repair + &Uninstall + &Close + Repair Successfully Completed + Uninstall Successfully Completed + Installation Successfully Completed + Setup Successful + &Launch + You must restart your computer before you can use the software. + &Restart + &Close + Setup Failed + Setup Failed + Uninstall Failed + Repair Failed + One or more issues caused the setup to fail. Please fix the issues and then retry setup. For more information see the <a href="#">log file</a>. + You must restart your computer to complete the rollback of the software. + &Restart + &Close + Files In Use + The following applications are using files that need to be updated: + Close the &applications and attempt to restart them. + &Do not close applications. A reboot will be required. + &OK + &Cancel + No action was taken as a system reboot is required. + diff --git a/dist/win/bundle/customBootstrapperTheme.xml b/dist/win/bundle/customBootstrapperTheme.xml new file mode 100644 index 000000000..adb90f8dd --- /dev/null +++ b/dist/win/bundle/customBootstrapperTheme.xml @@ -0,0 +1,100 @@ + + + + + + #(loc.Caption) + Segoe UI + Segoe UI + Segoe UI + Segoe UI + Segoe UI + + + #(loc.Title) + + + + #(loc.Title) + #(loc.HelpHeader) + #(loc.HelpText) + + + + #(loc.Title) + + #(loc.InstallHeader) + #(loc.InstallMessage) + + #(loc.InstallAcceptCheckbox) + + #(loc.InstallVersion) + + + + + + #(loc.Title) + #(loc.OptionsHeader) + #(loc.OptionsLocationLabel) + + + + + + + + #(loc.Title) + #(loc.FilesInUseHeader) + #(loc.FilesInUseLabel) + A + + + + + + + + + #(loc.Title) + + #(loc.ProgressHeader) + #(loc.ProgressLabel) + #(loc.OverallProgressPackageText) + + + + + + #(loc.Title) + #(loc.ModifyHeader) + + + + + + #(loc.Title) + + #(loc.SuccessHeader) + #(loc.SuccessInstallHeader) + #(loc.SuccessRepairHeader) + #(loc.SuccessUninstallHeader) + + #(loc.SuccessRestartText) + + + + + #(loc.Title) + + #(loc.FailureHeader) + #(loc.FailureInstallHeader) + #(loc.FailureUninstallHeader) + #(loc.FailureRepairHeader) + #(loc.FailureHyperlinkLogText) + + #(loc.FailureRestartText) + + + + diff --git a/dist/win/bundle/resources/Cryptomator.ico b/dist/win/bundle/resources/Cryptomator.ico new file mode 100644 index 0000000000000000000000000000000000000000..7d1d8be880abb6680387d2a67b814a0bd38368c4 GIT binary patch literal 162342 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 literal 0 HcmV?d00001 diff --git a/dist/win/bundle/resources/licenseTemplate.ftl b/dist/win/bundle/resources/licenseTemplate.ftl new file mode 100644 index 000000000..bd137a1a1 --- /dev/null +++ b/dist/win/bundle/resources/licenseTemplate.ftl @@ -0,0 +1,41 @@ +<#function artifactFormat p> + <#if p.name?index_of('Unnamed') > -1> + <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + <#else> + <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + + +{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} +{\colortbl ;\red0\green0\blue255;} +\viewkind4\uc1 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par +\par +\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par +\par +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par +\par +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\par +You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par +\par + +\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par +<#list licenseMap as e> +<#assign license = e.getKey()/> +<#assign projects = e.getValue()/> +<#if projects?size > 0> +\tab ${license}:\par +<#list projects as project> +\tab\tab- ${artifactFormat(project)}\par + + + +\par +\b Cryptomator uses other third-party assets under the following licenses:\b0\par +\tab SIL OFL 1.1 License:\par +\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par +\par +\b Cryptomator dynamically links to third-party libraries under the following license:\b0\par +\tab Uncategorized License:\par +\tab\tab - WinFsp - Windows File System Proxy, Copyright (C) Bill Zissimopoulos ({{\field{\*\fldinst{HYPERLINK https://github.com/billziss-gh/winfsp }}{\fldrslt{https://github.com/billziss-gh/winfsp\ul0\cf0}}}}\f0\fs16 )\b\par +} \ No newline at end of file diff --git a/dist/win/bundle/resources/logo.png b/dist/win/bundle/resources/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fdfdd6235347edf63ab057644211742d19b2dec1 GIT binary patch literal 2659 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 ReEt9c002ovPDHLkV1jVj0D;&SYDK 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 literal 0 HcmV?d00001 diff --git a/dist/win/resources/license.rtf b/dist/win/resources/license.rtf deleted file mode 100644 index 28956ed42..000000000 --- a/dist/win/resources/license.rtf +++ /dev/null @@ -1,84 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} -{\colortbl ;\red0\green0\blue255;} -{\*\generator Riched20 10.0.17134}\viewkind4\uc1 -\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par -\par -\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par -\par -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par -\par -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par -\par -You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par -\par -\b Cryptomator uses 40 third-party dependencies under the following licenses:\b0\par -\tab Apache License v2.0:\par -\tab\tab - jffi (com.github.jnr:jffi:1.2.23 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jffi }}{\fldrslt{http://github.com/jnr/jffi\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-constants (com.github.jnr:jnr-constants:0.9.15 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-constants }}{\fldrslt{http://github.com/jnr/jnr-constants\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-ffi }}{\fldrslt{http://github.com/jnr/jnr-ffi\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Gson (com.google.code.gson:gson:2.8.7 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/gson/gson }}{\fldrslt{https://github.com/google/gson/gson\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Dagger (com.google.dagger:dagger:2.38.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/dagger }}{\fldrslt{https://github.com/google/dagger\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/failureaccess }}{\fldrslt{https://github.com/google/guava/failureaccess\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Guava: Google Core Libraries for Java (com.google.guava:guava:30.1.1-jre - {{\field{\*\fldinst{HYPERLINK https://github.com/google/guava/guava }}{\fldrslt{https://github.com/google/guava/guava\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache Commons CLI (commons-cli:commons-cli:1.4 - {{\field{\*\fldinst{HYPERLINK http://commons.apache.org/proper/commons-cli/ }}{\fldrslt{http://commons.apache.org/proper/commons-cli/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javax.inject (javax.inject:javax.inject:1 - {{\field{\*\fldinst{HYPERLINK http://code.google.com/p/atinject/ }}{\fldrslt{http://code.google.com/p/atinject/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - {{\field{\*\fldinst{HYPERLINK https://commons.apache.org/proper/commons-lang/ }}{\fldrslt{https://commons.apache.org/proper/commons-lang/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - {{\field{\*\fldinst{HYPERLINK http://hc.apache.org/httpcomponents-core-ga }}{\fldrslt{http://hc.apache.org/httpcomponents-core-ga\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - {{\field{\*\fldinst{HYPERLINK http://jackrabbit.apache.org/jackrabbit-webdav/ }}{\fldrslt{http://jackrabbit.apache.org/jackrabbit-webdav/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par -\tab BSD:\par -\tab\tab - asm (org.ow2.asm:asm:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-analysis (org.ow2.asm:asm-analysis:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-commons (org.ow2.asm:asm-commons:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-tree (org.ow2.asm:asm-tree:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - asm-util (org.ow2.asm:asm-util:7.1 - {{\field{\*\fldinst{HYPERLINK http://asm.ow2.org/ }}{\fldrslt{http://asm.ow2.org/\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - Version 1.0:\par -\tab\tab - Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet-api }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet-api\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - Version 2.0:\par -\tab\tab - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-http }}{\fldrslt{https://eclipse.org/jetty/jetty-http\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-io }}{\fldrslt{https://eclipse.org/jetty/jetty-io\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-security }}{\fldrslt{https://eclipse.org/jetty/jetty-security\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-server }}{\fldrslt{https://eclipse.org/jetty/jetty-server\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-servlet }}{\fldrslt{https://eclipse.org/jetty/jetty-servlet\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - {{\field{\*\fldinst{HYPERLINK https://eclipse.org/jetty/jetty-util }}{\fldrslt{https://eclipse.org/jetty/jetty-util\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - v 1.0:\par -\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par -\tab Eclipse Public License - v 2.0:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab GNU Lesser General Public License:\par -\tab\tab - Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-classic }}{\fldrslt{http://logback.qos.ch/logback-classic\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Logback Core Module (ch.qos.logback:logback-core:1.2.3 - {{\field{\*\fldinst{HYPERLINK http://logback.qos.ch/logback-core }}{\fldrslt{http://logback.qos.ch/logback-core\ul0\cf0}}}}\f0\fs16 )\par -\tab GPLv2:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab GPLv2+CE:\par -\tab\tab - javafx-base (org.openjfx:javafx-base:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-base/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-base/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-controls (org.openjfx:javafx-controls:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-controls/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-controls/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-fxml (org.openjfx:javafx-fxml:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-fxml/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-fxml/\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - javafx-graphics (org.openjfx:javafx-graphics:16 - {{\field{\*\fldinst{HYPERLINK https://openjdk.java.net/projects/openjfx/javafx-graphics/ }}{\fldrslt{https://openjdk.java.net/projects/openjfx/javafx-graphics/\ul0\cf0}}}}\f0\fs16 )\par -\tab LGPL 2.1:\par -\tab\tab - jnr-posix (com.github.jnr:jnr-posix:3.0.54 - {{\field{\*\fldinst{HYPERLINK http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix }}{\fldrslt{http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access (net.java.dev.jna:jna:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - {{\field{\*\fldinst{HYPERLINK https://github.com/java-native-access/jna }}{\fldrslt{https://github.com/java-native-access/jna\ul0\cf0}}}}\f0\fs16 )\par -\tab MIT License:\par -\tab\tab - java jwt (com.auth0:java-jwt:3.18.1 - {{\field{\*\fldinst{HYPERLINK https://github.com/auth0/java-jwt }}{\fldrslt{https://github.com/auth0/java-jwt\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - {{\field{\*\fldinst{HYPERLINK http://github.com/jnr/jnr-x86asm }}{\fldrslt{http://github.com/jnr/jnr-x86asm\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - {{\field{\*\fldinst{HYPERLINK https://github.com/SerCeMan/jnr-fuse }}{\fldrslt{https://github.com/SerCeMan/jnr-fuse\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/nulab/zxcvbn4j }}{\fldrslt{https://github.com/nulab/zxcvbn4j\ul0\cf0}}}}\f0\fs16 )\par -\tab\tab - SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - {{\field{\*\fldinst{HYPERLINK http://www.slf4j.org }}{\fldrslt{http://www.slf4j.org\ul0\cf0}}}}\f0\fs16 )\par -\tab The BSD 2-Clause License:\par -\tab\tab - EasyBind (com.tobiasdiez:easybind:2.2 - {{\field{\*\fldinst{HYPERLINK https://github.com/tobiasdiez/EasyBind }}{\fldrslt{https://github.com/tobiasdiez/EasyBind\ul0\cf0}}}}\f0\fs16 )\par -\par -\b Cryptomator uses other third-party assets under the following licenses:\b0\par -\tab SIL OFL 1.1 License:\par -\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par -} diff --git a/dist/win/resources/licenseTemplate.ftl b/dist/win/resources/licenseTemplate.ftl new file mode 100644 index 000000000..d442e6538 --- /dev/null +++ b/dist/win/resources/licenseTemplate.ftl @@ -0,0 +1,37 @@ +<#function artifactFormat p> + <#if p.name?index_of('Unnamed') > -1> + <#return p.artifactId + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + <#else> + <#return p.name + " (" + p.groupId + ":" + p.artifactId + ":" + p.version + " - {{\\field{\\*\\fldinst{HYPERLINK " + (p.url!"no url defined") + "}}{\\fldrslt{" + (p.url!"no url defined") + "\\ul0\\cf0}}}}\\f0\\fs16 ) "> + + +{\rtf1\ansi\ansicpg1252\deff0\nouicompat{\fonttbl{\f0\fnil\fcharset0 Arial;}} +{\colortbl ;\red0\green0\blue255;} +\viewkind4\uc1 +\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\b\fs16\lang7 Cryptomator is distributed under the GPLv3 License, found below. Please see the bottom of this document for any other license applicable to code used within Cryptomator.\b0\par +\par +\b\'a9 2016 \endash 2022 Skymatic GmbH\b0\par +\par +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\par +\par +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\par +\par +You should have received a copy of the GNU General Public License along with this program. If not, see {{\field{\*\fldinst{HYPERLINK http://www.gnu.org/licenses/ }}{\fldrslt{http://www.gnu.org/licenses/\ul0\cf0}}}}\f0\fs16 .\par +\par + +\b Cryptomator uses ${dependencyMap?size} third-party dependencies under the following licenses:\b0\par +<#list licenseMap as e> +<#assign license = e.getKey()/> +<#assign projects = e.getValue()/> +<#if projects?size > 0> +\tab ${license}:\par +<#list projects as project> +\tab\tab- ${artifactFormat(project)}\par + + + +\par +\b Cryptomator uses other third-party assets under the following licenses:\b0\par +\tab SIL OFL 1.1 License:\par +\tab\tab - Font Awesome 5.12.0 ({{\field{\*\fldinst{HYPERLINK https://fontawesome.com/ }}{\fldrslt{https://fontawesome.com/\ul0\cf0}}}}\f0\fs16 )\b\par +} \ No newline at end of file From 8090f954959589d957c94de7f68f16e842f4adc8 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 23 Feb 2022 22:30:22 +0100 Subject: [PATCH 029/109] integrate windows installation bundle creation into gh action "release" --- .github/workflows/release.yml | 82 +++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fe6283ba..3ac783f56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -503,6 +503,7 @@ jobs: with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' - name: Patch Application Directory run: | cp dist/win/contrib/* appdir/Cryptomator @@ -519,6 +520,12 @@ jobs: timestampUrl: 'http://timestamp.digicert.com' folder: appdir/Cryptomator recursive: true + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win//resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/resources" - name: Create MSI run: > ${JAVA_HOME}/bin/jpackage @@ -559,13 +566,77 @@ jobs: path: installer/*.msi if-no-files-found: error +# +# Windows Cryptomator.exe bundle +# + win-exe: + name: Build Cryptomator.exe bundle + runs-on: windows-latest + needs: [win-msi, metadata] + steps: + - uses: actions/checkout@v2 + - name: Download Windows msi + uses: actions/download-artifact@v2 + with: + name: win-msi + path: dist/win/bundle/resources + - name: Strip version info from msi file name + run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/bundle/resources" + - name: Download winfsp + run: + curl --output dist/win/bundle/resources/winfsp.msi -L https://github.com/billziss-gh/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi + - name: Compile to wixObj file + run: > + "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs + -ext WixBalExtension + -out dist/win/bundle/ + -dBundleVersion="${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" + -dBundleVendor="Skymatic GmbH" + -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH" + -dAboutUrl="https://cryptomator.org" + -dHelpUrl="https://cryptomator.org/contact" + -dUpdateUrl="https://cryptomator.org/downloads/" + - name: Create executable with linker + run: > + "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj + -ext WixBalExtension + -out installer/Cryptomator.exe + - name: Codesign EXE + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: installer + - name: Add possible alpha/beta tags to installer name + run: mv installer/Cryptomator.exe installer/Cryptomator-${{ needs.metadata.outputs.semVerStr }}-x64.exe + - name: Upload win-exe + uses: actions/upload-artifact@v2 + with: + name: win-exe + path: installer/*.exe + if-no-files-found: error + # # Release # release: name: Draft a release on Github runs-on: ubuntu-latest - needs: [metadata,linux-appimage,mac-dmg,win-msi,ppa] + needs: [metadata,linux-appimage,mac-dmg,win-exe,ppa] if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator' steps: - uses: actions/checkout@v2 @@ -583,10 +654,14 @@ jobs: uses: actions/download-artifact@v2 with: name: win-msi + - name: Download Windows exe + uses: actions/download-artifact@v2 + with: + name: win-exe - name: Create detached GPG signature for all release files with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - for FILE in `find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.zsync" -o -name "*.tar.gz"`; do + for FILE in `find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.exe" -o -name "*.zsync" -o -name "*.tar.gz"`; do echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ${FILE} done env: @@ -594,7 +669,7 @@ jobs: GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Compute SHA256 checksums of release artifacts run: | - SHA256_SUMS=`find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.tar.gz" | xargs sha256sum` + SHA256_SUMS=`find . -name "*.AppImage" -o -name "*.dmg" -o -name "*.msi" -o -name "*.exe" -o -name "*.tar.gz" | xargs sha256sum` echo "SHA256_SUMS<> $GITHUB_ENV echo "${SHA256_SUMS}" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV @@ -612,6 +687,7 @@ jobs: *.asc *.dmg *.msi + *.exe body: |- :construction: Work in Progress ## What's New From 7cfcfda66f75cb73e4b92da53924b629a0e122a5 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Feb 2022 09:46:05 +0100 Subject: [PATCH 030/109] Apply suggestions from code review Co-authored-by: Sebastian Stenzel --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ac783f56..0bcfb8d35 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -524,7 +524,7 @@ jobs: run: > mvn -B license:add-third-party "-Dlicense.thirdPartyFilename=license.rtf" - "-Dlicense.fileTemplate=dist/win//resources/licenseTemplate.ftl" + "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" "-Dlicense.outputDirectory=dist/win/resources" - name: Create MSI run: > @@ -636,7 +636,7 @@ jobs: release: name: Draft a release on Github runs-on: ubuntu-latest - needs: [metadata,linux-appimage,mac-dmg,win-exe,ppa] + needs: [metadata,linux-appimage,mac-dmg,win-msi,win-exe,ppa] if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator' steps: - uses: actions/checkout@v2 From 771e3ab6f3873554301df76d3ef5a1fb2aea955f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 24 Feb 2022 09:53:26 +0100 Subject: [PATCH 031/109] remove unused ui elements from theme bundle --- dist/win/bundle/customBootstrapperTheme.xml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/dist/win/bundle/customBootstrapperTheme.xml b/dist/win/bundle/customBootstrapperTheme.xml index adb90f8dd..792f51a35 100644 --- a/dist/win/bundle/customBootstrapperTheme.xml +++ b/dist/win/bundle/customBootstrapperTheme.xml @@ -1,5 +1,6 @@ + @@ -27,21 +28,10 @@ #(loc.InstallMessage) #(loc.InstallAcceptCheckbox) - #(loc.InstallVersion) - - - #(loc.Title) - #(loc.OptionsHeader) - #(loc.OptionsLocationLabel) - - - - - #(loc.Title) From 0556bcd576941ecc53fafc752fb2fbb5b1257451 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 10:26:37 +0100 Subject: [PATCH 032/109] move metadata to "common" dir --- .../apps/org.cryptomator.Cryptomator.svg | 1 - .../{debian => common}/cryptomator-vault.xml | 0 .../org.cryptomator.Cryptomator.appdata.xml | 0 .../org.cryptomator.Cryptomator.desktop | 2 + .../org.cryptomator.Cryptomator.svg | 0 .../org.cryptomator.Cryptomator256.png} | Bin .../org.cryptomator.Cryptomator512.png} | Bin .../org.cryptomator.Cryptomator.appdata.xml | 69 ------------------ .../org.cryptomator.Cryptomator.desktop | 11 --- .../debian/org.cryptomator.Cryptomator.png | Bin 23272 -> 0 bytes 10 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg rename dist/linux/{debian => common}/cryptomator-vault.xml (100%) rename dist/linux/{appimage/resources/AppDir/usr/share/metainfo => common}/org.cryptomator.Cryptomator.appdata.xml (100%) rename dist/linux/{appimage/resources/AppDir/usr/share/applications => common}/org.cryptomator.Cryptomator.desktop (89%) rename dist/linux/{debian => common}/org.cryptomator.Cryptomator.svg (100%) rename dist/linux/{appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png => common/org.cryptomator.Cryptomator256.png} (100%) rename dist/linux/{appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png => common/org.cryptomator.Cryptomator512.png} (100%) delete mode 100644 dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml delete mode 100644 dist/linux/debian/org.cryptomator.Cryptomator.desktop delete mode 100644 dist/linux/debian/org.cryptomator.Cryptomator.png diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg deleted file mode 100644 index b2e12a3c3..000000000 --- a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dist/linux/debian/cryptomator-vault.xml b/dist/linux/common/cryptomator-vault.xml similarity index 100% rename from dist/linux/debian/cryptomator-vault.xml rename to dist/linux/common/cryptomator-vault.xml diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml similarity index 100% rename from dist/linux/appimage/resources/AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml rename to dist/linux/common/org.cryptomator.Cryptomator.appdata.xml diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop similarity index 89% rename from dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop rename to dist/linux/common/org.cryptomator.Cryptomator.desktop index 3e1b34830..81ddc3b4d 100644 --- a/dist/linux/appimage/resources/AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -1,10 +1,12 @@ [Desktop Entry] Name=Cryptomator +Version=${VERSION_STR} Comment=Cloud Storage Encryption Utility Exec=cryptomator %F Icon=org.cryptomator.Cryptomator Terminal=false Type=Application Categories=Utility;Security;FileTools; +StartupNotify=true StartupWMClass=org.cryptomator.launcher.Cryptomator MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata; diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.svg b/dist/linux/common/org.cryptomator.Cryptomator.svg similarity index 100% rename from dist/linux/debian/org.cryptomator.Cryptomator.svg rename to dist/linux/common/org.cryptomator.Cryptomator.svg diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator256.png similarity index 100% rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png rename to dist/linux/common/org.cryptomator.Cryptomator256.png diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png b/dist/linux/common/org.cryptomator.Cryptomator512.png similarity index 100% rename from dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png rename to dist/linux/common/org.cryptomator.Cryptomator512.png diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml deleted file mode 100644 index ad4af6c70..000000000 --- a/dist/linux/debian/org.cryptomator.Cryptomator.appdata.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - org.cryptomator.Cryptomator - FSFAP - GPL-3.0-or-later - Cryptomator -

Multi-platform client-side encryption tool optimized for cloud storages - -

- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud. -

-

- Features: -

    -
  • Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
  • -
  • Open Source means: No backdoors, control is better than trust
  • -
  • Client-side: No accounts, no data shared with any online service
  • -
  • Totally transparent: Just work on the virtual drive as if it were a USB flash drive
  • -
  • AES encryption with 256-bit key length
  • -
  • File names get encrypted
  • -
  • Folder structure gets obfuscated
  • -
  • Use as many vaults in your Dropbox as you want, each having individual passwords
  • -
  • One thousand commits for the security of your data!! :tada:
  • -
-

-

- Privacy: -

    -
  • 256-bit keys (unlimited strength policy bundled with native binaries)
  • -
  • Scrypt key derivation
  • -
  • Cryptographically secure random numbers for salts, IVs and the masterkey of course
  • -
  • Sensitive data is wiped from the heap asap
  • -
  • Lightweight: Complexity kills security
  • -
-

-

- Consistency: -

    -
  • HMAC over file contents to recognize changed ciphertext before decryption
  • -
  • I/O operations are transactional and atomic, if the filesystems support it
  • -
  • Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
  • -
-

-
- - Office - Security - FileTools - Java - - http://cryptomator.org - https://github.com/cryptomator/cryptomator/issues - https://community.cryptomator.org/c/kb/faq - https://community.cryptomator.org/ - https://cryptomator.org/ - - none - none - none - none - mild - - Cryptomator - - cryptomator - - org.cryptomator.Cryptomator.desktop - diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.desktop b/dist/linux/debian/org.cryptomator.Cryptomator.desktop deleted file mode 100644 index d8a5925bd..000000000 --- a/dist/linux/debian/org.cryptomator.Cryptomator.desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Name=Cryptomator -Version=${VERSION_STR} -Comment=Cloud Storage Encryption Utility -Exec=/usr/bin/cryptomator %f -Icon=org.cryptomator.Cryptomator -Terminal=false -Type=Application -Categories=Utility;Security;FileTools; -StartupWMClass=org.cryptomator.launcher.Cryptomator -MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata; \ No newline at end of file diff --git a/dist/linux/debian/org.cryptomator.Cryptomator.png b/dist/linux/debian/org.cryptomator.Cryptomator.png deleted file mode 100644 index 9c863511181b16dd12ad9df06d287e811c350e4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23272 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 Date: Thu, 24 Feb 2022 10:29:41 +0100 Subject: [PATCH 033/109] adjust appimage build to use "common" resources --- .github/workflows/release.yml | 7 +++++++ dist/linux/appimage/build.sh | 8 ++++++++ .../resources/AppDir/usr/share/applications/.gitkeep | 0 .../AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep | 0 .../AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep | 0 .../AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep | 0 .../appimage/resources/AppDir/usr/share/metainfo/.gitkeep | 0 dist/linux/common/org.cryptomator.Cryptomator.desktop | 2 +- 8 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fe6283ba..2c8d314ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -282,6 +282,13 @@ jobs: mv appdir/Cryptomator Cryptomator.AppDir cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh + envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop + cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg + cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop + cp dist/linux/common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml + cp dist/linux/common/cryptomator-vault.xml Cryptomator.AppDir/usr/share/mime/packages/cryptomator-vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index af1f2291f..101e78584 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -9,6 +9,7 @@ command -v mvn >/dev/null 2>&1 || { echo >&2 "mvn not found."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl not found."; exit 1; } VERSION=$(mvn -f ../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout) +SEMVER_STR=${VERSION} # compile mvn -B -f ../../../pom.xml clean package -DskipTests -Plinux @@ -54,6 +55,13 @@ mv Cryptomator Cryptomator.AppDir cp -r resources/AppDir/* Cryptomator.AppDir/ chmod +x Cryptomator.AppDir/lib/runtime/bin/java envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh +envsubst '${SEMVER_STR}' < ../common/org.cryptomator.Cryptomator.desktop > Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop +cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png +cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png +cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg +cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop +cp ../common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml +cp ../common/cryptomator-vault.xml Cryptomator.AppDir/usr/share/mime/packages/cryptomator-vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon diff --git a/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/applications/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/256x256/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/512x512/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/icons/hicolor/scalable/apps/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/metainfo/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/common/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop index 81ddc3b4d..8f584ca82 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Name=Cryptomator -Version=${VERSION_STR} +Version=${SEMVER_STR} Comment=Cloud Storage Encryption Utility Exec=cryptomator %F Icon=org.cryptomator.Cryptomator From 90da61e4950f5a98e3d713cc9fd03c8394f17370 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 10:41:40 +0100 Subject: [PATCH 034/109] adjust ppa build to use "common" resources --- .github/workflows/release.yml | 8 ++++---- dist/linux/debian/cryptomator.install | 11 ++++++----- dist/linux/debian/rules | 2 +- dist/linux/debian/source/include-binaries | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c8d314ac..73746ff17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,15 +221,15 @@ jobs: - name: patch and rename pkgdir run: | cp -r dist/linux/debian/ pkgdir - cp -r dist/linux/resources/ pkgdir + cp -r dist/linux/common/ pkgdir export RFC2822_TIMESTAMP=`date --rfc-2822` - envsubst '${VERSION_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules - envsubst '${VERSION_STR}' < dist/linux/debian/org.cryptomator.Cryptomator.desktop > pkgdir/debian/org.cryptomator.Cryptomator.desktop + envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules + envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > pkgdir/common/org.cryptomator.Cryptomator.desktop envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog find . -name "*.jar" >> pkgdir/debian/source/include-binaries mv pkgdir cryptomator_${{ needs.metadata.outputs.ppaVerStr }} env: - VERSION_STR: ${{ needs.metadata.outputs.semVerStr }} + SEMVER_STR: ${{ needs.metadata.outputs.semVerStr }} VERSION_NUM: ${{ needs.metadata.outputs.semVerNum }} REVISION_NUM: ${{ needs.metadata.outputs.revNum }} PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1 diff --git a/dist/linux/debian/cryptomator.install b/dist/linux/debian/cryptomator.install index 0d5e0b31c..38a469930 100644 --- a/dist/linux/debian/cryptomator.install +++ b/dist/linux/debian/cryptomator.install @@ -1,7 +1,8 @@ cryptomator usr/lib debian/cryptomator.sh usr/lib/cryptomator/bin -debian/org.cryptomator.Cryptomator.desktop usr/share/applications -debian/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps -debian/org.cryptomator.Cryptomator.png usr/share/icons/hicolor/512x512/apps -debian/org.cryptomator.Cryptomator.appdata.xml usr/share/metainfo -debian/cryptomator-vault.xml usr/share/mime/packages \ No newline at end of file +common/org.cryptomator.Cryptomator.desktop usr/share/applications +common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps +common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps +common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps +common/org.cryptomator.Cryptomator.appdata.xml usr/share/metainfo +common/cryptomator-vault.xml usr/share/mime/packages \ No newline at end of file diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index b381a2331..5aa254b0a 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -40,7 +40,7 @@ override_dh_auto_build: --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.showTrayIcon=false" \ --java-options "-Dcryptomator.buildNumber=\"ppa-${REVISION_NUM}\"" \ - --java-options "-Dcryptomator.appVersion=\"${VERSION_STR}\"" \ + --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ --verbose diff --git a/dist/linux/debian/source/include-binaries b/dist/linux/debian/source/include-binaries index adc7cabd8..6425cc04a 100644 --- a/dist/linux/debian/source/include-binaries +++ b/dist/linux/debian/source/include-binaries @@ -1,2 +1,2 @@ -debian/org.cryptomator.Cryptomator.png -resources/cryptomator.png +common/org.cryptomator.Cryptomator256.png +common/org.cryptomator.Cryptomator512.png \ No newline at end of file From 43e936d117a45cc4739ea1a008d06f0b7b7c9a9e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 10:56:23 +0100 Subject: [PATCH 035/109] rename mimetype file --- .github/workflows/release.yml | 2 +- dist/linux/appimage/build.sh | 2 +- ...ptomator-vault.xml => application-vnd.cryptomator.vault.xml} | 2 +- dist/linux/common/org.cryptomator.Cryptomator.desktop | 2 +- dist/linux/debian/cryptomator.install | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename dist/linux/common/{cryptomator-vault.xml => application-vnd.cryptomator.vault.xml} (77%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73746ff17..55e871ad7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -288,7 +288,7 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml - cp dist/linux/common/cryptomator-vault.xml Cryptomator.AppDir/usr/share/mime/packages/cryptomator-vault.xml + cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 101e78584..9a5e55246 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -61,7 +61,7 @@ cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/ico cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp ../common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml -cp ../common/cryptomator-vault.xml Cryptomator.AppDir/usr/share/mime/packages/cryptomator-vault.xml +cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon diff --git a/dist/linux/common/cryptomator-vault.xml b/dist/linux/common/application-vnd.cryptomator.vault.xml similarity index 77% rename from dist/linux/common/cryptomator-vault.xml rename to dist/linux/common/application-vnd.cryptomator.vault.xml index eeb4bc537..3b602f230 100644 --- a/dist/linux/common/cryptomator-vault.xml +++ b/dist/linux/common/application-vnd.cryptomator.vault.xml @@ -1,6 +1,6 @@ - + Cryptomator Vault Metadata diff --git a/dist/linux/common/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop index 8f584ca82..a5cc454e4 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -9,4 +9,4 @@ Type=Application Categories=Utility;Security;FileTools; StartupNotify=true StartupWMClass=org.cryptomator.launcher.Cryptomator -MimeType=application/vnd.cryptomator.encrypted;application/x-vnd.cryptomator.vault-metadata; +MimeType=application/vnd.cryptomator.encrypted;application/vnd.cryptomator.vault; diff --git a/dist/linux/debian/cryptomator.install b/dist/linux/debian/cryptomator.install index 38a469930..17aa7e869 100644 --- a/dist/linux/debian/cryptomator.install +++ b/dist/linux/debian/cryptomator.install @@ -5,4 +5,4 @@ common/org.cryptomator.Cryptomator.svg usr/share/icons/hicolor/scalable/apps common/org.cryptomator.Cryptomator256.png usr/share/icons/hicolor/256x256/apps common/org.cryptomator.Cryptomator512.png usr/share/icons/hicolor/512x512/apps common/org.cryptomator.Cryptomator.appdata.xml usr/share/metainfo -common/cryptomator-vault.xml usr/share/mime/packages \ No newline at end of file +common/application-vnd.cryptomator.vault.xml usr/share/mime/packages \ No newline at end of file From ec1c255bb402cb6bb228cbd57e9a5f5bdc2273a9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 10:56:36 +0100 Subject: [PATCH 036/109] update appstream metadata --- .../org.cryptomator.Cryptomator.appdata.xml | 136 +++++++++--------- 1 file changed, 71 insertions(+), 65 deletions(-) diff --git a/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml index ad4af6c70..0340bc83e 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml @@ -1,69 +1,75 @@ - org.cryptomator.Cryptomator - FSFAP - GPL-3.0-or-later - Cryptomator - Multi-platform client-side encryption tool optimized for cloud storages - -

- Cryptomator offers multi-platform transparent client-side encryption of your files in the cloud. -

-

- Features: -

    -
  • Works with Dropbox, Google Drive, OneDrive, ownCloud, Nextcloud and any other cloud storage service which synchronizes with a local directory
  • -
  • Open Source means: No backdoors, control is better than trust
  • -
  • Client-side: No accounts, no data shared with any online service
  • -
  • Totally transparent: Just work on the virtual drive as if it were a USB flash drive
  • -
  • AES encryption with 256-bit key length
  • -
  • File names get encrypted
  • -
  • Folder structure gets obfuscated
  • -
  • Use as many vaults in your Dropbox as you want, each having individual passwords
  • -
  • One thousand commits for the security of your data!! :tada:
  • -
-

-

- Privacy: -

    -
  • 256-bit keys (unlimited strength policy bundled with native binaries)
  • -
  • Scrypt key derivation
  • -
  • Cryptographically secure random numbers for salts, IVs and the masterkey of course
  • -
  • Sensitive data is wiped from the heap asap
  • -
  • Lightweight: Complexity kills security
  • -
-

-

- Consistency: -

    -
  • HMAC over file contents to recognize changed ciphertext before decryption
  • -
  • I/O operations are transactional and atomic, if the filesystems support it
  • -
  • Each file contains all information needed for decryption (except for the key of course), no common metadata means no Single Point of Failure
  • -
-

-
- - Office - Security - FileTools - Java - - http://cryptomator.org - https://github.com/cryptomator/cryptomator/issues - https://community.cryptomator.org/c/kb/faq - https://community.cryptomator.org/ - https://cryptomator.org/ - - none - none - none - none - mild - - Cryptomator - - cryptomator - - org.cryptomator.Cryptomator.desktop + org.cryptomator.Cryptomator + FSFAP + GPL-3.0-or-later + Cryptomator + Multi-platform client-side encryption tool optimized for cloud storages + + +

+ Cryptomator provides transparent, client-side encryption for your cloud. Protect your documents from unauthorized + access. Cryptomator is free and open source software, so you can rest assured there are no backdoors. +

+

+ Cryptomator encrypts file contents and names using AES. Your passphrase is protected against bruteforcing attempts + using scrypt. Directory structures get obfuscated. The only thing which cannot be encrypted without breaking your + cloud synchronization is the modification date of your files. +

+

+ Cryptomator is a free and open source software licensed under the GPLv3. This allows anyone to check our code. It + is impossible to introduce backdoors for third parties. Also we cannot hide vulnerabilities. And the best thing + is: There is no need to trust us, as you can control us! +

+

+ Vendor lock-ins are impossible. Even if we decided to stop development: The source code is already cloned by + hundreds of other developers. As you don't need an account, you will never stand in front of locked doors. +

+
+ + + Office + Security + FileTools + + + org.cryptomator.Cryptomator.desktop + + cryptomator + application/vnd.cryptomator.vault + application/vnd.cryptomator.encrypted + + + + + Light theme + https://cryptomator.org/presskit/linux-screenshot-1.png + + + Dark theme + https://cryptomator.org/presskit/linux-screenshot-2.png + + + + https://cryptomator.org/ + https://github.com/cryptomator/cryptomator/issues/ + https://cryptomator.org/donate + https://community.cryptomator.org/c/kb/faq + https://community.cryptomator.org/ + https://translate.cryptomator.org + + Skymatic GmbH + + + none + none + none + none + mild + + + + +
From 69ff0e44f516f6fafe031a330850d0e77b7cfe48 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 12:00:02 +0100 Subject: [PATCH 037/109] fix build --- .../appimage/resources/AppDir/usr/share/mime/packages/.gitkeep | 0 dist/linux/common/org.cryptomator.Cryptomator.svg | 2 +- dist/linux/debian/source/include-binaries | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep diff --git a/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep b/dist/linux/appimage/resources/AppDir/usr/share/mime/packages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/dist/linux/common/org.cryptomator.Cryptomator.svg b/dist/linux/common/org.cryptomator.Cryptomator.svg index 19d80d49c..c76d99fb9 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.svg +++ b/dist/linux/common/org.cryptomator.Cryptomator.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/dist/linux/debian/source/include-binaries b/dist/linux/debian/source/include-binaries index 6425cc04a..8b9254c65 100644 --- a/dist/linux/debian/source/include-binaries +++ b/dist/linux/debian/source/include-binaries @@ -1,2 +1,2 @@ common/org.cryptomator.Cryptomator256.png -common/org.cryptomator.Cryptomator512.png \ No newline at end of file +common/org.cryptomator.Cryptomator512.png From 7d9aab46a868f3652a67b54beaa1d9b1747af3d3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 16:27:28 +0100 Subject: [PATCH 038/109] fix broken path during mimetype installation --- dist/linux/debian/postinst | 2 +- dist/linux/debian/prerm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/linux/debian/postinst b/dist/linux/debian/postinst index 2af574c6b..5668a5e29 100644 --- a/dist/linux/debian/postinst +++ b/dist/linux/debian/postinst @@ -24,7 +24,7 @@ case "$1" in mkdir -p /usr/share/desktop-directories fi xdg-desktop-menu install --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop - xdg-mime install /usr/share/mime/packages/cryptomator-vault.xml + xdg-mime install /usr/share/mime/packages/application-vnd.cryptomator.vault.xml ;; abort-upgrade|abort-remove|abort-deconfigure) diff --git a/dist/linux/debian/prerm b/dist/linux/debian/prerm index cace6816e..41a54cf33 100644 --- a/dist/linux/debian/prerm +++ b/dist/linux/debian/prerm @@ -22,7 +22,7 @@ case "$1" in echo Removing shortcut xdg-desktop-menu uninstall --novendor /usr/share/applications/org.cryptomator.Cryptomator.desktop - xdg-mime uninstall /usr/share/mime/packages/cryptomator-vault.xml + xdg-mime uninstall /usr/share/mime/packages/application-vnd.cryptomator.vault.xml ;; failed-upgrade) From 891b5597de0d37cbeede2c1797cfd5f9c185eebb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 24 Feb 2022 16:29:21 +0100 Subject: [PATCH 039/109] fix build, add common/ to debian orig.tar.xz --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55e871ad7..f6870c974 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -216,15 +216,16 @@ jobs: with: name: linux-buildkit path: pkgdir - - name: create orig.tar.gz - run: tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . + - name: create orig.tar.gz with common/ libs/ mods/ + run: | + cp -r dist/linux/common/ pkgdir + envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > pkgdir/common/org.cryptomator.Cryptomator.desktop + tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . - name: patch and rename pkgdir run: | cp -r dist/linux/debian/ pkgdir - cp -r dist/linux/common/ pkgdir export RFC2822_TIMESTAMP=`date --rfc-2822` envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules - envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > pkgdir/common/org.cryptomator.Cryptomator.desktop envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog find . -name "*.jar" >> pkgdir/debian/source/include-binaries mv pkgdir cryptomator_${{ needs.metadata.outputs.ppaVerStr }} @@ -286,7 +287,6 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg - cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg From 02fe63c7bc604132a62abb32feb8f52bb472b210 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 25 Feb 2022 09:25:15 +0100 Subject: [PATCH 040/109] version of the spec, not version of the software see https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s06.html --- .github/workflows/release.yml | 3 +-- dist/linux/appimage/build.sh | 1 - dist/linux/common/org.cryptomator.Cryptomator.desktop | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f6870c974..6f6b6745f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,7 +219,6 @@ jobs: - name: create orig.tar.gz with common/ libs/ mods/ run: | cp -r dist/linux/common/ pkgdir - envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > pkgdir/common/org.cryptomator.Cryptomator.desktop tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . - name: patch and rename pkgdir run: | @@ -283,11 +282,11 @@ jobs: mv appdir/Cryptomator Cryptomator.AppDir cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh - envsubst '${SEMVER_STR}' < dist/linux/common/org.cryptomator.Cryptomator.desktop > Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg cp dist/linux/common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml + cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.desktop cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 9a5e55246..2473e23ae 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -55,7 +55,6 @@ mv Cryptomator Cryptomator.AppDir cp -r resources/AppDir/* Cryptomator.AppDir/ chmod +x Cryptomator.AppDir/lib/runtime/bin/java envsubst '${REVISION_NO}' < resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh -envsubst '${SEMVER_STR}' < ../common/org.cryptomator.Cryptomator.desktop > Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg diff --git a/dist/linux/common/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop index a5cc454e4..fad034a33 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Name=Cryptomator -Version=${SEMVER_STR} +Version=1.0.0 Comment=Cloud Storage Encryption Utility Exec=cryptomator %F Icon=org.cryptomator.Cryptomator From 10999b27252d8d4f6a73611a456d9c46fc70ffed Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 25 Feb 2022 09:28:58 +0100 Subject: [PATCH 041/109] renamed .appdata.xml to .metainfo.xml as suggested by @x80486 --- .github/workflows/release.yml | 2 +- dist/linux/appimage/build.sh | 2 +- ...tor.appdata.xml => org.cryptomator.Cryptomator.metainfo.xml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename dist/linux/common/{org.cryptomator.Cryptomator.appdata.xml => org.cryptomator.Cryptomator.metainfo.xml} (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f6b6745f..d45fde7be 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -285,7 +285,7 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg - cp dist/linux/common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml + cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.desktop cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index 2473e23ae..6dd670df2 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -59,7 +59,7 @@ cp ../common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/ico cp ../common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp ../common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg cp ../common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop -cp ../common/org.cryptomator.Cryptomator.appdata.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.appdata.xml +cp ../common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml cp ../common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg diff --git a/dist/linux/common/org.cryptomator.Cryptomator.appdata.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml similarity index 100% rename from dist/linux/common/org.cryptomator.Cryptomator.appdata.xml rename to dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml From 6553e48ea3efa7ae3a4de2437378b22f16401b82 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 25 Feb 2022 09:33:15 +0100 Subject: [PATCH 042/109] use oars-1.1 as suggested by @x80486 --- dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index 0340bc83e..1c3ae9c27 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -61,11 +61,7 @@ Skymatic GmbH - - none - none - none - none + mild From 7afc17e34e077277d756d3ed7ae57219abedcda9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 25 Feb 2022 09:43:10 +0100 Subject: [PATCH 043/109] fix path --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d45fde7be..905c252a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -286,7 +286,7 @@ jobs: cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml - cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.desktop + cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg From 8ee47aad0f122a196a0a9fbf7c34322f09375453 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 25 Feb 2022 09:58:47 +0100 Subject: [PATCH 044/109] remove version entirely (should have been 1.0, but not required) --- dist/linux/common/org.cryptomator.Cryptomator.desktop | 1 - 1 file changed, 1 deletion(-) diff --git a/dist/linux/common/org.cryptomator.Cryptomator.desktop b/dist/linux/common/org.cryptomator.Cryptomator.desktop index fad034a33..1872b9f38 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.desktop +++ b/dist/linux/common/org.cryptomator.Cryptomator.desktop @@ -1,6 +1,5 @@ [Desktop Entry] Name=Cryptomator -Version=1.0.0 Comment=Cloud Storage Encryption Utility Exec=cryptomator %F Icon=org.cryptomator.Cryptomator From 55d1a8e9352b745a8d01c912bd2037253a8b1a86 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 25 Feb 2022 13:21:45 +0100 Subject: [PATCH 045/109] Allow custom mount point for winfsp --- .../mountpoint/CustomMountPointChooser.java | 58 ++++++++++++++----- .../vaultoptions/MountOptionsController.java | 46 +++++---------- .../resources/fxml/vault_options_mount.fxml | 4 +- 3 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index c55ede640..d075e1292 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.settings.VolumeImpl; +import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Volume; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,9 +13,7 @@ import javax.inject.Inject; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; -import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; @@ -35,7 +34,6 @@ class CustomMountPointChooser implements MountPointChooser { @Override public boolean isApplicable(Volume caller) { - //Disable if useExperimentalFuse is required (Win + Fuse), but set to false return caller.getImplementationType() != VolumeImpl.FUSE || !SystemUtils.IS_OS_WINDOWS || environment.useExperimentalFuse(); } @@ -48,8 +46,16 @@ class CustomMountPointChooser implements MountPointChooser { @Override public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException { switch (caller.getMountPointRequirement()) { - case PARENT_NO_MOUNT_POINT -> prepareParentNoMountPoint(mountPoint); - case EMPTY_MOUNT_POINT -> prepareEmptyMountPoint(mountPoint); + case PARENT_NO_MOUNT_POINT -> { + prepareParentNoMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + return true; + } + case EMPTY_MOUNT_POINT -> { + prepareEmptyMountPoint(mountPoint); + LOG.debug("Successfully checked custom mount point: {}", mountPoint); + return false; + } case NONE -> { //Requirement "NONE" doesn't make any sense here. //No need to prepare/verify a Mountpoint without requiring one... @@ -60,21 +66,26 @@ class CustomMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } } - LOG.debug("Successfully checked custom mount point: {}", mountPoint); - return false; } private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { //This the case on Windows when using FUSE //See https://github.com/billziss-gh/winfsp/issues/320 - Path parent = mountPoint.getParent(); - if (!Files.isDirectory(parent)) { - throw new InvalidMountPointException(new NotDirectoryException(parent.toString())); - } - //We must use #notExists() here because notExists =/= !exists (see docs) - if (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) { - //File exists OR can't be determined - throw new InvalidMountPointException(new FileAlreadyExistsException(mountPoint.toString())); + assert SystemUtils.IS_OS_WINDOWS; + + Path hideaway = getHideaway(mountPoint); + if (Files.exists(hideaway)) { + LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount."); + } else if (!Files.isDirectory(mountPoint)) { + throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); //simulate we need a directory + } else { + //TODO: should we require it to be empty? + try { + Files.move(mountPoint, hideaway); + Files.setAttribute(hideaway, "dos:hidden", true); + } catch (IOException e) { + throw new InvalidMountPointException(e); + } } } @@ -92,4 +103,21 @@ class CustomMountPointChooser implements MountPointChooser { } } + @Override + public void cleanup(Volume caller, Path mountPoint) { + if (VolumeImpl.FUSE == caller.getImplementationType() && MountPointRequirement.PARENT_NO_MOUNT_POINT == caller.getMountPointRequirement()) { + Path hideaway = getHideaway(mountPoint); + try { + Files.move(hideaway, mountPoint); + Files.setAttribute(mountPoint, "dos:hidden", false); + } catch (IOException e) { + LOG.error("Unable to clean up mountpoint {} for Winfsp mounting."); + } + } + } + + private Path getHideaway(Path mountPoint) { + return mountPoint.resolveSibling(mountPoint.getFileName().toString() + "_tmp"); + } + } diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index 8739be791..fc1962b9d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -11,9 +11,6 @@ import org.cryptomator.ui.common.FxController; import javax.inject.Inject; import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.StringProperty; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -32,18 +29,16 @@ import java.nio.file.Path; import java.util.ResourceBundle; import java.util.Set; -/** - * TODO: if WebDav is selected on a windows system, custom mount directory is _not_ supported. This is currently not indicated/shown/etc in the ui - */ @VaultOptionsScoped public class MountOptionsController implements FxController { private final Stage window; private final Vault vault; - private final BooleanProperty osIsWindows = new SimpleBooleanProperty(SystemUtils.IS_OS_WINDOWS); - private final BooleanBinding webDavAndWindows; + private final boolean webDavAndWindows; + private final boolean fuseAndWindows; private final WindowsDriveLetters windowsDriveLetters; private final ResourceBundle resourceBundle; + public CheckBox readOnlyCheckbox; public CheckBox customMountFlagsCheckbox; public TextField mountFlags; @@ -53,20 +48,14 @@ public class MountOptionsController implements FxController { public RadioButton mountPointCustomDir; public ChoiceBox driveLetterSelection; - //FUSE + Windows -> Disable some (experimental) features for the user because they are unstable - //Use argument Dfuse.experimental="true" to override - private final BooleanBinding restrictToStableFuseOnWindows; - @Inject MountOptionsController(@VaultOptionsWindow Stage window, @VaultOptionsWindow Vault vault, Settings settings, WindowsDriveLetters windowsDriveLetters, ResourceBundle resourceBundle, Environment environment) { this.window = window; this.vault = vault; - this.webDavAndWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.WEBDAV).and(osIsWindows); + this.webDavAndWindows = settings.preferredVolumeImpl().get() == VolumeImpl.WEBDAV && SystemUtils.IS_OS_WINDOWS; + this.fuseAndWindows = settings.preferredVolumeImpl().get() == VolumeImpl.FUSE && SystemUtils.IS_OS_WINDOWS; this.windowsDriveLetters = windowsDriveLetters; this.resourceBundle = resourceBundle; - - BooleanBinding isFuseOnWindows = settings.preferredVolumeImpl().isEqualTo(VolumeImpl.FUSE).and(osIsWindows); - this.restrictToStableFuseOnWindows = isFuseOnWindows.and(new SimpleBooleanProperty(!environment.useExperimentalFuse())); //Is FUSE on Win and is NOT experimental fuse enabled } @FXML @@ -74,10 +63,11 @@ public class MountOptionsController implements FxController { // readonly: readOnlyCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().usesReadOnlyMode()); - if (getRestrictToStableFuseOnWindows()) { + //TODO: support this feature on Windows + if (fuseAndWindows) { readOnlyCheckbox.setSelected(false); // to prevent invalid states + readOnlyCheckbox.setDisable(true); } - readOnlyCheckbox.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().or(restrictToStableFuseOnWindows)); // custom mount flags: mountFlags.disableProperty().bind(customMountFlagsCheckbox.selectedProperty().not()); @@ -95,9 +85,7 @@ public class MountOptionsController implements FxController { driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle)); driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get()); - if (vault.getVaultSettings().useCustomMountPath().get() - && vault.getVaultSettings().getCustomMountPath().isPresent() - && !getRestrictToStableFuseOnWindows() /* to prevent invalid states */) { + if (vault.getVaultSettings().useCustomMountPath().get() && vault.getVaultSettings().getCustomMountPath().isPresent()) { mountPoint.selectToggle(mountPointCustomDir); } else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) { mountPoint.selectToggle(mountPointWinDriveLetter); @@ -188,20 +176,16 @@ public class MountOptionsController implements FxController { // Getter & Setter - public BooleanProperty osIsWindowsProperty() { - return osIsWindows; - } - public boolean getOsIsWindows() { - return osIsWindows.get(); + return SystemUtils.IS_OS_WINDOWS; } - public BooleanBinding webDavAndWindowsProperty() { + public boolean getCustomMountPointSupported() { return webDavAndWindows; } - public boolean isWebDavAndWindows() { - return webDavAndWindows.get(); + public boolean getReadOnlySupported() { + return fuseAndWindows; } public StringProperty customMountPathProperty() { @@ -212,8 +196,4 @@ public class MountOptionsController implements FxController { return vault.getVaultSettings().customMountPath().get(); } - public Boolean getRestrictToStableFuseOnWindows() { - return restrictToStableFuseOnWindows.get(); - } - } diff --git a/src/main/resources/fxml/vault_options_mount.fxml b/src/main/resources/fxml/vault_options_mount.fxml index a96784020..a0d1ce0e1 100644 --- a/src/main/resources/fxml/vault_options_mount.fxml +++ b/src/main/resources/fxml/vault_options_mount.fxml @@ -42,8 +42,8 @@ - - + + - + From e15b68fc9be800d250739520849c6544668f4bb8 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 28 Feb 2022 18:54:01 +0100 Subject: [PATCH 052/109] Refactor winfsp mount preps and add unit tests --- .../mountpoint/CustomMountPointChooser.java | 45 ++++-- .../CustomMountPointChooserTest.java | 133 ++++++++++++++++++ 2 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index d075e1292..872815a4e 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -13,7 +13,9 @@ import javax.inject.Inject; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.nio.file.Paths; @@ -21,6 +23,8 @@ import java.util.Optional; class CustomMountPointChooser implements MountPointChooser { + private static final String HIDEAWAY_PREFIX = ".~$"; + private static final String HIDEAWAY_SUFFIX = ".tmp"; private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class); private final VaultSettings vaultSettings; @@ -68,19 +72,41 @@ class CustomMountPointChooser implements MountPointChooser { } } - private void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { + void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { //This the case on Windows when using FUSE //See https://github.com/billziss-gh/winfsp/issues/320 assert SystemUtils.IS_OS_WINDOWS; Path hideaway = getHideaway(mountPoint); - if (Files.exists(hideaway)) { - LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount."); - } else if (!Files.isDirectory(mountPoint)) { - throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); //simulate we need a directory - } else { - //TODO: should we require it to be empty? + + var mpExists = Files.exists(mountPoint); + var hideExists = Files.exists(hideaway); + + //both resources exist (whatever type) + //TODO: possible improvement by just deleting an _empty_ hideaway + if (mpExists && hideExists) { + throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString())); + } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist + throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString())); + } else if (!mpExists) { //only hideaway exists + + if (!Files.isDirectory(hideaway)) { + throw new InvalidMountPointException(new NotDirectoryException(hideaway.toString())); + } + LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint); try { + Files.setAttribute(hideaway, "dos:hidden", true); + } catch (IOException e) { + throw new InvalidMountPointException(e); + } + } else { + if (!Files.isDirectory(mountPoint)) { + throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); + } + try { + if(Files.list(mountPoint).findFirst().isPresent()) { + throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString())); + } Files.move(mountPoint, hideaway); Files.setAttribute(hideaway, "dos:hidden", true); } catch (IOException e) { @@ -116,8 +142,9 @@ class CustomMountPointChooser implements MountPointChooser { } } - private Path getHideaway(Path mountPoint) { - return mountPoint.resolveSibling(mountPoint.getFileName().toString() + "_tmp"); + //visible for testing + Path getHideaway(Path mountPoint) { + return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX); } } diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java new file mode 100644 index 000000000..f5be1522b --- /dev/null +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -0,0 +1,133 @@ +package org.cryptomator.common.mountpoint; + +import org.cryptomator.common.Environment; +import org.cryptomator.common.settings.VaultSettings; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CustomMountPointChooserTest { + + //--- Mocks --- + VaultSettings vaultSettings; + Environment environment; + + CustomMountPointChooser customMpc; + + + @BeforeEach + public void init() { + this.vaultSettings = Mockito.mock(VaultSettings.class); + this.environment = Mockito.mock(Environment.class); + this.customMpc = new CustomMountPointChooser(vaultSettings, environment); + } + + @Nested + class WinfspPreperations { + + @Test + @DisplayName("Test MP preparation for winfsp, if only mountpoint is present") + public void testPrepareParentNoMountpointOnlyMountpoint(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + Files.createDirectory(mntPoint); + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.notExists(mntPoint)); + + Path hideaway = customMpc.getHideaway(mntPoint); + Assertions.assertTrue(Files.exists(hideaway)); + Assertions.assertNotEquals(hideaway.getFileName().toString(), "mntPoint"); + Assertions.assertTrue(hideaway.getFileName().toString().contains("mntPoint")); + + Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + + @Test + @DisplayName("Test MP preparation for winfsp, if only non-empty mountpoint is present") + public void testPrepareParentNoMountpointOnlyNonEmptyMountpoint(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + Files.createDirectory(mntPoint); + Files.createFile(mntPoint.resolve("foo")); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(mntPoint.resolve("foo"))); + } + + @Test + @DisplayName("Test MP preparation for Winfsp, if for any reason only hideaway dir is present") + public void testPrepareParentNoMountpointOnlyHideaway(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + Files.createDirectory(hideaway); //we explicitly do not set the file attributes here + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(hideaway)); + Assertions.assertNotEquals(hideaway.getFileName().toString(), "mntPoint"); + Assertions.assertTrue(hideaway.getFileName().toString().contains("mntPoint")); + + Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + + @Test + @DisplayName("Test Winfsp MP preparation, if mountpoint and hideaway dirs are present") + public void testPrepareParentNoMountpointMountPointAndHideaway(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + Files.createDirectory(hideaway); //we explicitly do not set the file attributes here + Files.createDirectory(mntPoint); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(hideaway)); + Assertions.assertTrue(Files.exists(mntPoint)); + + Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); + Assertions.assertFalse((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } + + @Test + @DisplayName("Test Winfsp MP preparation, if neither mountpoint nor hideaway dir is present") + public void testPrepareParentNoMountpointNothing(@TempDir Path tmpDir) { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + //execute + Assertions.assertThrows(InvalidMountPointException.class, () -> customMpc.prepareParentNoMountPoint(mntPoint)); + + //evaluate + Assertions.assertTrue(Files.notExists(hideaway)); + Assertions.assertTrue(Files.notExists(mntPoint)); + } + + } + + +} From ed1459a0f0dcae95b3c802231ded285b10eef4c1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 1 Mar 2022 08:49:47 +0100 Subject: [PATCH 053/109] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3caa9b899..8975b8693 100644 --- a/pom.xml +++ b/pom.xml @@ -39,7 +39,7 @@ 17.0.2 3.12.0 - 3.18.2 + 3.18.3 2.2 31.0-jre 2.40.3 From df7d9ba79e32b693b2c362d6874f734b7e55382a Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 1 Mar 2022 15:25:00 +0100 Subject: [PATCH 054/109] Update winfsp download links --- .github/workflows/release.yml | 2 +- dist/win/build.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0bcfb8d35..a6502d201 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -595,7 +595,7 @@ jobs: "-Dlicense.outputDirectory=dist/win/bundle/resources" - name: Download winfsp run: - curl --output dist/win/bundle/resources/winfsp.msi -L https://github.com/billziss-gh/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi + curl --output dist/win/bundle/resources/winfsp.msi -L https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi - name: Compile to wixObj file run: > "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index c3cb4ba84..0b0e953bc 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -125,7 +125,7 @@ $Env:JP_WIXWIZARD_RESOURCES = "$buildDir\resources" # download Winfsp [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $ProgressPreference = 'SilentlyContinue' # disables Invoke-WebRequest's progress bar, which slows down downloads to a few bytes/s -$winfspMsiUrl = "https://github.com/billziss-gh/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi" +$winfspMsiUrl = "https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi" Write-Output "Downloading ${winfspMsiUrl}..." Invoke-WebRequest $winfspMsiUrl -OutFile ".\bundle\resources\winfsp.msi" # redirects are followed by default From 4f4c9924930a4c1fab67133e21fb863e34c2ba2d Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Mar 2022 14:40:27 +0100 Subject: [PATCH 055/109] Further improvements: * make PARENT_NO_MOUNTPOINT preps system agnostic * add unit tests for cleanup --- .../mountpoint/CustomMountPointChooser.java | 21 ++++---- .../CustomMountPointChooserTest.java | 53 +++++++++++++++++-- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 872815a4e..82898d59e 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -72,13 +72,10 @@ class CustomMountPointChooser implements MountPointChooser { } } + //This the case on Windows when using FUSE + //See https://github.com/billziss-gh/winfsp/issues/320 void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { - //This the case on Windows when using FUSE - //See https://github.com/billziss-gh/winfsp/issues/320 - assert SystemUtils.IS_OS_WINDOWS; - Path hideaway = getHideaway(mountPoint); - var mpExists = Files.exists(mountPoint); var hideExists = Files.exists(hideaway); @@ -104,11 +101,13 @@ class CustomMountPointChooser implements MountPointChooser { throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); } try { - if(Files.list(mountPoint).findFirst().isPresent()) { + if (Files.list(mountPoint).findFirst().isPresent()) { throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString())); } Files.move(mountPoint, hideaway); - Files.setAttribute(hideaway, "dos:hidden", true); + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(hideaway, "dos:hidden", true); + } } catch (IOException e) { throw new InvalidMountPointException(e); } @@ -131,13 +130,15 @@ class CustomMountPointChooser implements MountPointChooser { @Override public void cleanup(Volume caller, Path mountPoint) { - if (VolumeImpl.FUSE == caller.getImplementationType() && MountPointRequirement.PARENT_NO_MOUNT_POINT == caller.getMountPointRequirement()) { + if (caller.getMountPointRequirement() == MountPointRequirement.PARENT_NO_MOUNT_POINT) { Path hideaway = getHideaway(mountPoint); try { Files.move(hideaway, mountPoint); - Files.setAttribute(mountPoint, "dos:hidden", false); + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(mountPoint, "dos:hidden", false); + } } catch (IOException e) { - LOG.error("Unable to clean up mountpoint {} for Winfsp mounting."); + LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e); } } } diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java index f5be1522b..2126b16b6 100644 --- a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -1,7 +1,10 @@ package org.cryptomator.common.mountpoint; +import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; +import org.cryptomator.common.vaults.MountPointRequirement; +import org.cryptomator.common.vaults.Volume; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; @@ -10,6 +13,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.IOException; @@ -21,12 +25,14 @@ public class CustomMountPointChooserTest { //--- Mocks --- VaultSettings vaultSettings; Environment environment; + Volume volume; CustomMountPointChooser customMpc; @BeforeEach public void init() { + this.volume = Mockito.mock(Volume.class); this.vaultSettings = Mockito.mock(VaultSettings.class); this.environment = Mockito.mock(Environment.class); this.customMpc = new CustomMountPointChooser(vaultSettings, environment); @@ -36,7 +42,7 @@ public class CustomMountPointChooserTest { class WinfspPreperations { @Test - @DisplayName("Test MP preparation for winfsp, if only mountpoint is present") + @DisplayName("PARENT_NO_MOUNTPOINT preparations succeeds, if only mountpoint is present") public void testPrepareParentNoMountpointOnlyMountpoint(@TempDir Path tmpDir) throws IOException { //prepare var mntPoint = tmpDir.resolve("mntPoint"); @@ -58,7 +64,7 @@ public class CustomMountPointChooserTest { } @Test - @DisplayName("Test MP preparation for winfsp, if only non-empty mountpoint is present") + @DisplayName("PARENT_NO_MOUNTPOINT preparations fail, if only non-empty mountpoint is present") public void testPrepareParentNoMountpointOnlyNonEmptyMountpoint(@TempDir Path tmpDir) throws IOException { //prepare var mntPoint = tmpDir.resolve("mntPoint"); @@ -73,7 +79,7 @@ public class CustomMountPointChooserTest { } @Test - @DisplayName("Test MP preparation for Winfsp, if for any reason only hideaway dir is present") + @DisplayName("PARENT_NO_MOUNTPOINT preparation succeeds, if for any reason only hideaway dir is present") public void testPrepareParentNoMountpointOnlyHideaway(@TempDir Path tmpDir) throws IOException { //prepare var mntPoint = tmpDir.resolve("mntPoint"); @@ -93,7 +99,7 @@ public class CustomMountPointChooserTest { } @Test - @DisplayName("Test Winfsp MP preparation, if mountpoint and hideaway dirs are present") + @DisplayName("PARENT_NO_MOUNTPOINT preparation fails, if mountpoint and hideaway dirs are present") public void testPrepareParentNoMountpointMountPointAndHideaway(@TempDir Path tmpDir) throws IOException { //prepare var mntPoint = tmpDir.resolve("mntPoint"); @@ -113,7 +119,7 @@ public class CustomMountPointChooserTest { } @Test - @DisplayName("Test Winfsp MP preparation, if neither mountpoint nor hideaway dir is present") + @DisplayName("PARENT_NO_MOUNTPOINT preparation fails, if neither mountpoint nor hideaway dir is present") public void testPrepareParentNoMountpointNothing(@TempDir Path tmpDir) { //prepare var mntPoint = tmpDir.resolve("mntPoint"); @@ -127,6 +133,43 @@ public class CustomMountPointChooserTest { Assertions.assertTrue(Files.notExists(mntPoint)); } + @Test + @DisplayName("Normal Cleanup for PARENT_NO_MOUNTPOINT") + public void testCleanupSuccess(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + Files.createDirectory(hideaway); + Mockito.when(volume.getMountPointRequirement()).thenReturn(MountPointRequirement.PARENT_NO_MOUNT_POINT); + + //execute + Assertions.assertDoesNotThrow(() -> customMpc.cleanup(volume, mntPoint)); + + //evaluate + Assertions.assertTrue(Files.exists(mntPoint)); + Assertions.assertTrue(Files.notExists(hideaway)); + + Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); + Assertions.assertFalse((Boolean) Files.getAttribute(mntPoint, "dos:hidden")); + } + + @Test + @DisplayName("On IOException cleanup for PARENT_NO_MOUNTPOINT exits normally") + public void testCleanupIOFailure(@TempDir Path tmpDir) throws IOException { + //prepare + var mntPoint = tmpDir.resolve("mntPoint"); + var hideaway = customMpc.getHideaway(mntPoint); + + Files.createDirectory(hideaway); + Mockito.when(volume.getMountPointRequirement()).thenReturn(MountPointRequirement.PARENT_NO_MOUNT_POINT); + try (MockedStatic filesMock = Mockito.mockStatic(Files.class)) { + filesMock.when(() -> Files.move(Mockito.any(), Mockito.any(), Mockito.any())).thenThrow(new IOException("error")); + //execute + Assertions.assertDoesNotThrow(() -> customMpc.cleanup(volume, mntPoint)); + } + } + } From f148973bef18bbdf64f80ff958ad74cec80ace67 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Mar 2022 14:50:26 +0100 Subject: [PATCH 056/109] fix wrong visibillity --- .../common/mountpoint/CustomMountPointChooserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java index 2126b16b6..2ee969363 100644 --- a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -39,7 +39,7 @@ public class CustomMountPointChooserTest { } @Nested - class WinfspPreperations { + public class WinfspPreperations { @Test @DisplayName("PARENT_NO_MOUNTPOINT preparations succeeds, if only mountpoint is present") From fba0df10f9503f0460aa0311d09de6710f13b9f9 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Mar 2022 15:28:01 +0100 Subject: [PATCH 057/109] Resolve code smells and a bug Co-authored-by: sonarcloud --- .../mountpoint/CustomMountPointChooser.java | 51 ++++++++++--------- .../vaultoptions/MountOptionsController.java | 4 +- .../CustomMountPointChooserTest.java | 18 +++++-- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 82898d59e..d174e5dc6 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -12,7 +12,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -25,6 +24,7 @@ class CustomMountPointChooser implements MountPointChooser { private static final String HIDEAWAY_PREFIX = ".~$"; private static final String HIDEAWAY_SUFFIX = ".tmp"; + private static final String WIN_HIDDEN = "dos:hidden"; private static final Logger LOG = LoggerFactory.getLogger(CustomMountPointChooser.class); private final VaultSettings vaultSettings; @@ -79,34 +79,27 @@ class CustomMountPointChooser implements MountPointChooser { var mpExists = Files.exists(mountPoint); var hideExists = Files.exists(hideaway); - //both resources exist (whatever type) //TODO: possible improvement by just deleting an _empty_ hideaway - if (mpExists && hideExists) { + if (mpExists && hideExists) { //both resources exist (whatever type) throw new InvalidMountPointException(new FileAlreadyExistsException(hideaway.toString())); } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString())); } else if (!mpExists) { //only hideaway exists - - if (!Files.isDirectory(hideaway)) { - throw new InvalidMountPointException(new NotDirectoryException(hideaway.toString())); - } + isDirectory(hideaway); LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint); try { - Files.setAttribute(hideaway, "dos:hidden", true); + Files.setAttribute(hideaway, WIN_HIDDEN, true); } catch (IOException e) { throw new InvalidMountPointException(e); } - } else { - if (!Files.isDirectory(mountPoint)) { - throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); - } + } else { //only mountpoint exists try { - if (Files.list(mountPoint).findFirst().isPresent()) { - throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString())); - } + isDirectory(mountPoint); + isEmpty(mountPoint); + Files.move(mountPoint, hideaway); if (SystemUtils.IS_OS_WINDOWS) { - Files.setAttribute(hideaway, "dos:hidden", true); + Files.setAttribute(hideaway, WIN_HIDDEN, true); } } catch (IOException e) { throw new InvalidMountPointException(e); @@ -116,13 +109,9 @@ class CustomMountPointChooser implements MountPointChooser { private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException { //This is the case for Windows when using Dokany and for Linux and Mac - if (!Files.isDirectory(mountPoint)) { - throw new InvalidMountPointException(new NotDirectoryException(mountPoint.toString())); - } - try (DirectoryStream ds = Files.newDirectoryStream(mountPoint)) { - if (ds.iterator().hasNext()) { - throw new InvalidMountPointException(new DirectoryNotEmptyException(mountPoint.toString())); - } + isDirectory(mountPoint); + try { + isEmpty(mountPoint); } catch (IOException exception) { throw new InvalidMountPointException("IOException while checking folder content", exception); } @@ -135,7 +124,7 @@ class CustomMountPointChooser implements MountPointChooser { try { Files.move(hideaway, mountPoint); if (SystemUtils.IS_OS_WINDOWS) { - Files.setAttribute(mountPoint, "dos:hidden", false); + Files.setAttribute(mountPoint, WIN_HIDDEN, false); } } catch (IOException e) { LOG.error("Unable to clean up mountpoint {} for Winfsp mounting.", mountPoint, e); @@ -143,6 +132,20 @@ class CustomMountPointChooser implements MountPointChooser { } } + private void isDirectory(Path toCheck) throws InvalidMountPointException { + if (!Files.isDirectory(toCheck)) { + throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString())); + } + } + + private void isEmpty(Path toCheck) throws InvalidMountPointException, IOException { + try (var dirStream = Files.list(toCheck)) { + if (dirStream.findFirst().isPresent()) { + throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString())); + } + } + } + //visible for testing Path getHideaway(Path mountPoint) { return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX); diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java index bce186511..3be38568d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MountOptionsController.java @@ -183,7 +183,7 @@ public class MountOptionsController implements FxController { } public boolean isReadOnlySupported() { - return !(usedVolumeImpl == VolumeImpl.FUSE && isOsWindows()) ; + return !(usedVolumeImpl == VolumeImpl.FUSE && isOsWindows()); } public StringProperty customMountPathProperty() { @@ -191,7 +191,7 @@ public class MountOptionsController implements FxController { } public boolean isCustomMountOptionsSupported() { - return !(usedVolumeImpl == VolumeImpl.WEBDAV); + return usedVolumeImpl != VolumeImpl.WEBDAV; } public String getCustomMountPath() { diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java index 2ee969363..2954c9355 100644 --- a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -1,6 +1,5 @@ package org.cryptomator.common.mountpoint; -import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.Environment; import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.MountPointRequirement; @@ -41,6 +40,19 @@ public class CustomMountPointChooserTest { @Nested public class WinfspPreperations { + @Test + @DisplayName("Hideaway name for PARENT_NO_MOUNTPOINT is not the same as mountpoint") + public void testGetHideaway() { + //prepare + Path mntPoint = Path.of("/foo/bar"); + //execute + var hideaway = customMpc.getHideaway(mntPoint); + //eval + Assertions.assertNotEquals(hideaway.getFileName(), mntPoint.getFileName()); + Assertions.assertEquals(hideaway.getParent(), mntPoint.getParent()); + Assertions.assertTrue(hideaway.getFileName().toString().contains(mntPoint.getFileName().toString())); + } + @Test @DisplayName("PARENT_NO_MOUNTPOINT preparations succeeds, if only mountpoint is present") public void testPrepareParentNoMountpointOnlyMountpoint(@TempDir Path tmpDir) throws IOException { @@ -56,8 +68,6 @@ public class CustomMountPointChooserTest { Path hideaway = customMpc.getHideaway(mntPoint); Assertions.assertTrue(Files.exists(hideaway)); - Assertions.assertNotEquals(hideaway.getFileName().toString(), "mntPoint"); - Assertions.assertTrue(hideaway.getFileName().toString().contains("mntPoint")); Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); @@ -91,8 +101,6 @@ public class CustomMountPointChooserTest { //evaluate Assertions.assertTrue(Files.exists(hideaway)); - Assertions.assertNotEquals(hideaway.getFileName().toString(), "mntPoint"); - Assertions.assertTrue(hideaway.getFileName().toString().contains("mntPoint")); Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); From 14dc026354730ede3866e2f167166d0e1bd63c09 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 2 Mar 2022 17:24:54 +0100 Subject: [PATCH 058/109] Cleanup Co-authored-by: Sebastian Stenzel --- .../mountpoint/CustomMountPointChooser.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index d174e5dc6..9cfc8652f 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.NotDirectoryException; import java.nio.file.Path; @@ -49,16 +50,16 @@ class CustomMountPointChooser implements MountPointChooser { @Override public boolean prepare(Volume caller, Path mountPoint) throws InvalidMountPointException { - switch (caller.getMountPointRequirement()) { + return switch (caller.getMountPointRequirement()) { case PARENT_NO_MOUNT_POINT -> { prepareParentNoMountPoint(mountPoint); LOG.debug("Successfully checked custom mount point: {}", mountPoint); - return true; + yield true; } case EMPTY_MOUNT_POINT -> { prepareEmptyMountPoint(mountPoint); LOG.debug("Successfully checked custom mount point: {}", mountPoint); - return false; + yield false; } case NONE -> { //Requirement "NONE" doesn't make any sense here. @@ -69,15 +70,15 @@ class CustomMountPointChooser implements MountPointChooser { //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT" throw new InvalidMountPointException(new IllegalStateException("Not implemented")); } - } + }; } - //This the case on Windows when using FUSE + //This is case on Windows when using FUSE //See https://github.com/billziss-gh/winfsp/issues/320 void prepareParentNoMountPoint(Path mountPoint) throws InvalidMountPointException { Path hideaway = getHideaway(mountPoint); - var mpExists = Files.exists(mountPoint); - var hideExists = Files.exists(hideaway); + var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS); + var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS); //TODO: possible improvement by just deleting an _empty_ hideaway if (mpExists && hideExists) { //both resources exist (whatever type) @@ -85,21 +86,23 @@ class CustomMountPointChooser implements MountPointChooser { } else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist throw new InvalidMountPointException(new NoSuchFileException(mountPoint.toString())); } else if (!mpExists) { //only hideaway exists - isDirectory(hideaway); + checkIsDirectory(hideaway); LOG.info("Mountpoint {} for winfsp mount seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint); try { - Files.setAttribute(hideaway, WIN_HIDDEN, true); + if (SystemUtils.IS_OS_WINDOWS) { + Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS); + } } catch (IOException e) { throw new InvalidMountPointException(e); } } else { //only mountpoint exists try { - isDirectory(mountPoint); - isEmpty(mountPoint); + checkIsDirectory(mountPoint); + checkIsEmpty(mountPoint); Files.move(mountPoint, hideaway); if (SystemUtils.IS_OS_WINDOWS) { - Files.setAttribute(hideaway, WIN_HIDDEN, true); + Files.setAttribute(hideaway, WIN_HIDDEN, true, LinkOption.NOFOLLOW_LINKS); } } catch (IOException e) { throw new InvalidMountPointException(e); @@ -109,9 +112,9 @@ class CustomMountPointChooser implements MountPointChooser { private void prepareEmptyMountPoint(Path mountPoint) throws InvalidMountPointException { //This is the case for Windows when using Dokany and for Linux and Mac - isDirectory(mountPoint); + checkIsDirectory(mountPoint); try { - isEmpty(mountPoint); + checkIsEmpty(mountPoint); } catch (IOException exception) { throw new InvalidMountPointException("IOException while checking folder content", exception); } @@ -132,13 +135,13 @@ class CustomMountPointChooser implements MountPointChooser { } } - private void isDirectory(Path toCheck) throws InvalidMountPointException { - if (!Files.isDirectory(toCheck)) { + private void checkIsDirectory(Path toCheck) throws InvalidMountPointException { + if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) { throw new InvalidMountPointException(new NotDirectoryException(toCheck.toString())); } } - private void isEmpty(Path toCheck) throws InvalidMountPointException, IOException { + private void checkIsEmpty(Path toCheck) throws InvalidMountPointException, IOException { try (var dirStream = Files.list(toCheck)) { if (dirStream.findFirst().isPresent()) { throw new InvalidMountPointException(new DirectoryNotEmptyException(toCheck.toString())); From 0c6f4297a27364a910404b2b125b019ca3cbf8fa Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 3 Mar 2022 10:40:40 +0100 Subject: [PATCH 059/109] display version as `x.y.z deb-1234` instead of `x.y.z ppa-1234` --- dist/linux/debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index d3119fb5b..e4f824394 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -42,7 +42,7 @@ override_dh_auto_build: --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" \ --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" \ --java-options "-Dcryptomator.showTrayIcon=false" \ - --java-options "-Dcryptomator.buildNumber=\"ppa-${REVISION_NUM}\"" \ + --java-options "-Dcryptomator.buildNumber=\"deb-${REVISION_NUM}\"" \ --java-options "-Dcryptomator.appVersion=\"${SEMVER_STR}\"" \ --app-version "${VERSION_NUM}.${REVISION_NUM}" \ --resource-dir resources \ From 962b4f28afcbf14a0f0c17caa61f7af646f7ebf9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 3 Mar 2022 11:59:59 +0100 Subject: [PATCH 060/109] simplify dput using globs --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17934113e..50a7030bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -277,9 +277,7 @@ jobs: name: linux-deb-package path: . - name: dput to beta repo - run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_${PPA_VERSION}_source.changes - env: - PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1 + run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes # # Linux Cryptomator.AppImage From 3f596b385361c62ac1f67379ae0a50eb613d1b6a Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 3 Mar 2022 12:45:12 +0100 Subject: [PATCH 061/109] attempt to fix dput --- .github/workflows/release.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50a7030bd..f98d9b60c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -256,6 +256,7 @@ jobs: cryptomator_*.dsc cryptomator_*.orig.tar.xz cryptomator_*.debian.tar.xz + cryptomator_*_source.buildinfo cryptomator_*_source.changes cryptomator_*_amd64.deb @@ -271,6 +272,10 @@ jobs: run: | sudo apt-get update sudo apt-get install dput + - name: import public key + run: curl -sSL ${GPG_PUBLIC_KEY_URL} | gpg --import - + env: + GPG_PUBLIC_KEY_URL: https://gist.githubusercontent.com/cryptobot/211111cf092037490275f39d408f461a/raw/E6E6A235.asc - name: download linux-deb-package uses: actions/download-artifact@v2 with: From 9b001b507155fddea7494409064e3998f8f9d77e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 3 Mar 2022 17:45:21 +0100 Subject: [PATCH 062/109] Co-authored-by: Sebastian Stenzel --- .../mountpoint/CustomMountPointChooser.java | 8 +------ .../CustomMountPointChooserTest.java | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java index 9cfc8652f..80c5b067b 100644 --- a/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java +++ b/src/main/java/org/cryptomator/common/mountpoint/CustomMountPointChooser.java @@ -61,15 +61,9 @@ class CustomMountPointChooser implements MountPointChooser { LOG.debug("Successfully checked custom mount point: {}", mountPoint); yield false; } - case NONE -> { - //Requirement "NONE" doesn't make any sense here. - //No need to prepare/verify a Mountpoint without requiring one... + case NONE, UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT -> { throw new InvalidMountPointException(new IllegalStateException("Illegal MountPointRequirement")); } - default -> { - //Currently the case for "UNUSED_ROOT_DIR, PARENT_OPT_MOUNT_POINT" - throw new InvalidMountPointException(new IllegalStateException("Not implemented")); - } }; } diff --git a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java index 2954c9355..da2e0fde0 100644 --- a/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java +++ b/src/test/java/org/cryptomator/common/mountpoint/CustomMountPointChooserTest.java @@ -5,7 +5,6 @@ import org.cryptomator.common.settings.VaultSettings; import org.cryptomator.common.vaults.MountPointRequirement; import org.cryptomator.common.vaults.Volume; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -69,8 +68,9 @@ public class CustomMountPointChooserTest { Path hideaway = customMpc.getHideaway(mntPoint); Assertions.assertTrue(Files.exists(hideaway)); - Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); - Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } } @Test @@ -102,8 +102,9 @@ public class CustomMountPointChooserTest { //evaluate Assertions.assertTrue(Files.exists(hideaway)); - Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); - Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertTrue((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } } @Test @@ -122,8 +123,9 @@ public class CustomMountPointChooserTest { Assertions.assertTrue(Files.exists(hideaway)); Assertions.assertTrue(Files.exists(mntPoint)); - Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); - Assertions.assertFalse((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertFalse((Boolean) Files.getAttribute(hideaway, "dos:hidden")); + } } @Test @@ -158,8 +160,9 @@ public class CustomMountPointChooserTest { Assertions.assertTrue(Files.exists(mntPoint)); Assertions.assertTrue(Files.notExists(hideaway)); - Assumptions.assumeTrue(OS.WINDOWS.isCurrentOs()); - Assertions.assertFalse((Boolean) Files.getAttribute(mntPoint, "dos:hidden")); + if(OS.WINDOWS.isCurrentOs()) { + Assertions.assertFalse((Boolean) Files.getAttribute(mntPoint, "dos:hidden")); + } } @Test From d9af387a69502de65ca60e5508c29bb9e67e37bd Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 7 Mar 2022 08:46:46 +0100 Subject: [PATCH 063/109] updated public key url [ci skip] --- .github/workflows/release.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5a241da4..a24342da3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -273,9 +273,7 @@ jobs: sudo apt-get update sudo apt-get install dput - name: import public key - run: curl -sSL ${GPG_PUBLIC_KEY_URL} | gpg --import - - env: - GPG_PUBLIC_KEY_URL: https://gist.githubusercontent.com/cryptobot/211111cf092037490275f39d408f461a/raw/E6E6A235.asc + run: curl -sSL https://github.com/cryptobot.gpg | gpg --import - - name: download linux-deb-package uses: actions/download-artifact@v2 with: From a32f5bb6d90b163a130d025546657d35eca26d47 Mon Sep 17 00:00:00 2001 From: Ralph Plawetzki Date: Mon, 7 Mar 2022 11:25:40 +0100 Subject: [PATCH 064/109] Use updated Cryptomator screenshots (1.6.5) They do appstream-util validate --- dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml index 1c3ae9c27..bdd0e178e 100644 --- a/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml +++ b/dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml @@ -44,11 +44,11 @@ Light theme - https://cryptomator.org/presskit/linux-screenshot-1.png + https://user-images.githubusercontent.com/11858409/156986109-6e58f59c-8b8c-4501-b33b-bb1e33007cea.png Dark theme - https://cryptomator.org/presskit/linux-screenshot-2.png + https://user-images.githubusercontent.com/11858409/156986113-6c5d7801-86e0-4643-bc2f-aff9d95d3ce0.png From 79e6a4cd488c4136229c533dd002c53141442f41 Mon Sep 17 00:00:00 2001 From: Kevin St-Sauveur Date: Mon, 14 Mar 2022 02:29:25 -0400 Subject: [PATCH 065/109] Modify vault title when unlocked --- .../java/org/cryptomator/ui/traymenu/TrayMenuController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java index 4ce3808c4..66be4c652 100644 --- a/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java +++ b/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java @@ -91,6 +91,8 @@ class TrayMenuController { unlockItem.addActionListener(createActionListenerForVault(vault, this::unlockVault)); submenu.add(unlockItem); } else if (vault.isUnlocked()) { + submenu.setLabel("*".concat(submenu.getLabel())); + MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock")); lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault)); submenu.add(lockItem); From c29cc9ab85f582c458ce981559d3b6f7bfd82803 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 16 Mar 2022 15:08:09 +0100 Subject: [PATCH 066/109] supress false positive in dependency-check plugin --- pom.xml | 5 ++++- suppression.xml | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8975b8693..1cfd73ac7 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,9 @@ 5.8.1 3.12.4 2.2 + + + 7.0.0 @@ -263,7 +266,7 @@ org.owasp dependency-check-maven - 6.3.1 + ${dependency-check.version} diff --git a/suppression.xml b/suppression.xml index c747f92a7..ccd1a1cdf 100644 --- a/suppression.xml +++ b/suppression.xml @@ -25,4 +25,23 @@ org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 .* + + + + ^org\.cryptomator:.*$ + cpe:/a:cryptomator:cryptomator + CVE-2022-25366 + + + + + ^commons\-cli:commons\-cli:.*$ + cpe:/a:apache:james + + cpe:/a:spirit-project:spirit + \ No newline at end of file From f231c25dfc471ebde04d4697a7bbf38fdb3ceafd Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 16 Mar 2022 15:08:46 +0100 Subject: [PATCH 067/109] Update maven plugin dependencies --- pom.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 1cfd73ac7..469ab6398 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,7 @@ 7.0.0 + 0.8.7 @@ -231,7 +232,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.10.1 org.apache.maven.plugins @@ -241,7 +242,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.3.0 org.apache.maven.plugins @@ -256,12 +257,12 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.2.2 org.jacoco jacoco-maven-plugin - 0.8.7 + ${jacoco.version} org.owasp From d3e92395316b5f3a4d83ab27ab54e510e7b64684 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 16 Mar 2022 15:24:55 +0100 Subject: [PATCH 068/109] simplify name of test file for location check on vault creation --- .../ui/addvaultwizard/CreateNewVaultLocationController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 35f2be069..1fd463432 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -35,14 +35,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ResourceBundle; -import java.util.UUID; @AddVaultWizardScoped public class CreateNewVaultLocationController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); - private static final String TEMP_FILE_FORMAT = "cryptomator-%s.tmp"; + private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp"; private final Stage window; private final Lazy chooseNameScene; @@ -112,7 +111,7 @@ public class CreateNewVaultLocationController implements FxController { } private boolean isActuallyWritable(Path p) { - Path tmpFile = p.resolve(String.format(TEMP_FILE_FORMAT, UUID.randomUUID())); + Path tmpFile = p.resolve(TEMP_FILE_FORMAT); try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { return true; } catch (IOException e) { From 6c176d5484d7844cabad147ea991387521b38789 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 10:31:45 +0100 Subject: [PATCH 069/109] separate workflow for building .AppImage --- .github/workflows/appimage.yml | 149 +++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 .github/workflows/appimage.yml diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml new file mode 100644 index 000000000..c74650745 --- /dev/null +++ b/.github/workflows/appimage.yml @@ -0,0 +1,149 @@ +name: Build AppImage + +on: + release: + types: [published] + workflow_dispatch: + inputs: + semver: + description: 'SemVer' + required: true + default: '0.99.99-SNAPSHOT' + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Run Maven Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + else + SEM_VER_STR=${{ github.event.inputs.semver }} + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + mvn versions:set -DnewVersion=${SEM_VER_STR} + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Build and Test + run: mvn -B clean package -Pdependency-check,linux + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/linux/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" + --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" + --java-options "-Dcryptomator.showTrayIcon=false" + --java-options "-Dcryptomator.buildNumber=\"appimage-${{ steps.versions.outputs.revNum }}\"" + --resource-dir dist/linux/resources + - name: Patch Cryptomator.AppDir + run: | + mv appdir/Cryptomator Cryptomator.AppDir + cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ + envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh + cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png + cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg + cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml + cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop + cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg + ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon + ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop + ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun + env: + REVISION_NO: ${{ steps.versions.outputs.revNum }} + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 + run: | + JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` + ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/ + mv jni/x86_64-Linux/* lib/app/libjffi.so + working-directory: Cryptomator.AppDir + - name: Download AppImageKit + run: | + curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage + chmod +x appimagetool.AppImage + ./appimagetool.AppImage --appimage-extract + - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Build AppImage + run: > + ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage + -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' + --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" + - name: Upload AppImage + uses: actions/upload-artifact@v2 + with: + name: linux-appimage + path: | + cryptomator-*.AppImage + cryptomator-*.AppImage.zsync + if-no-files-found: error + - name: Publish AppImage on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + *.AppImage + *.zsync + *.asc \ No newline at end of file From ba037007a47b837b099492369c3edd157077ecfa Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 10:34:41 +0100 Subject: [PATCH 070/109] run workflow on push (otherwise it won't show up) --- .github/workflows/appimage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index c74650745..8da9b2da1 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -1,6 +1,7 @@ name: Build AppImage on: + push: # TODO remove before merging into develop release: types: [published] workflow_dispatch: From 0f5a358c42f2065c577f986f72f1049d6d59a7ac Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 10:37:29 +0100 Subject: [PATCH 071/109] read version from pom.xml (except for tagged commits) --- .github/workflows/appimage.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 8da9b2da1..6bde439c8 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -5,18 +5,13 @@ on: release: types: [published] workflow_dispatch: - inputs: - semver: - description: 'SemVer' - required: true - default: '0.99.99-SNAPSHOT' env: JAVA_VERSION: 17 jobs: build: - name: Run Maven Build + name: Build AppImage runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -32,12 +27,12 @@ jobs: run: | if [[ $GITHUB_REF == refs/tags/* ]]; then SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} else - SEM_VER_STR=${{ github.event.inputs.semver }} + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` fi SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` REVCOUNT=`git rev-list --count HEAD` - mvn versions:set -DnewVersion=${SEM_VER_STR} echo "::set-output name=semVerStr::${SEM_VER_STR}" echo "::set-output name=semVerNum::${SEM_VER_NUM}" echo "::set-output name=revNum::${REVCOUNT}" From e078869f33b482462ad237d2c7baac090b0d7536 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 10:51:09 +0100 Subject: [PATCH 072/109] Create .asc signatures --- .github/workflows/appimage.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 6bde439c8..dac5bbf99 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -39,14 +39,14 @@ jobs: - uses: skymatic/semver-validation-action@v1 with: version: ${{ steps.versions.outputs.semVerStr }} - - name: Build and Test - run: mvn -B clean package -Pdependency-check,linux + - name: Run maven + run: mvn -B clean package -Pdependency-check,linux -DskipTests - name: Patch target dir run: | cp LICENSE.txt target cp dist/linux/launcher.sh target cp target/cryptomator-*.jar target/mods - - name: jlink + - name: Run jlink run: > ${JAVA_HOME}/bin/jlink --verbose @@ -57,7 +57,7 @@ jobs: --no-man-pages --strip-debug --compress=1 - - name: jpackage + - name: Run jpackage run: > ${JAVA_HOME}/bin/jpackage --verbose @@ -125,13 +125,18 @@ jobs: ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ steps.versions.outputs.semVerStr }}-x86_64.AppImage -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" + - name: Create detached GPG signatures + run: | + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync - name: Upload AppImage uses: actions/upload-artifact@v2 with: - name: linux-appimage + name: appimage path: | cryptomator-*.AppImage cryptomator-*.AppImage.zsync + cryptomator-*.asc if-no-files-found: error - name: Publish AppImage on GitHub Releases if: startsWith(github.ref, 'refs/tags/') From 47a206cf252282f3814c67d42723e2f6f7217992 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 11:16:46 +0100 Subject: [PATCH 073/109] separate workflow for building .deb --- .github/workflows/appimage.yml | 3 +- .github/workflows/debian.yml | 111 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/debian.yml diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index dac5bbf99..3e3e33e34 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -1,7 +1,6 @@ name: Build AppImage on: - push: # TODO remove before merging into develop release: types: [published] workflow_dispatch: @@ -129,7 +128,7 @@ jobs: run: | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync - - name: Upload AppImage + - name: Upload artifacts uses: actions/upload-artifact@v2 with: name: appimage diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml new file mode 100644 index 000000000..3d1cfcdc2 --- /dev/null +++ b/.github/workflows/debian.yml @@ -0,0 +1,111 @@ +name: Build Debian Package + +on: + push: # TODO remove before merging into develop + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Build Debian Package + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Install build tools + run: | + sudo apt-get update + sudo apt-get install debhelper devscripts dput + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}" + - uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,linux -DskipTests + - name: create orig.tar.gz with common/ libs/ mods/ + run: | + mkdir pkgdir + cp -r target/libs pkgdir + cp -r target/mods pkgdir + cp -r dist/linux/common/ pkgdir + cp target/cryptomator-*.jar pkgdir/mods + tar -cJf cryptomator_${{ steps.versions.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . + - name: Patch and rename pkgdir + run: | + cp -r dist/linux/debian/ pkgdir + export RFC2822_TIMESTAMP=`date --rfc-2822` + envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules + envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog + find . -name "*.jar" >> pkgdir/debian/source/include-binaries + mv pkgdir cryptomator_${{ steps.versions.outputs.ppaVerStr }} + env: + SEMVER_STR: ${{ steps.versions.outputs.semVerStr }} + VERSION_NUM: ${{ steps.versions.outputs.semVerNum }} + REVISION_NUM: ${{ steps.versions.outputs.revNum }} + PPA_VERSION: ${{ steps.versions.outputs.ppaVerStr }}-0ppa1 + - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: debuild + run: | + debuild -S -sa -d + debuild -b -sa -d + env: + DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback + DEBSIGN_KEYID: 615D449FE6E6A235 + working-directory: cryptomator_${{ steps.versions.outputs.ppaVerStr }} + - name: Create detached GPG signatures + run: | + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: linux-deb-package + path: | + cryptomator_*.dsc + cryptomator_*.orig.tar.xz + cryptomator_*.debian.tar.xz + cryptomator_*_source.buildinfo + cryptomator_*_source.changes + cryptomator_*_amd64.deb + cryptomator_*.asc + - name: Run dput to beta repo + # if: startsWith(github.ref, 'refs/tags/') + run: dput --check-only ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes + - name: Publish AppImage on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + cryptomator_*_amd64.deb + cryptomator_*.asc \ No newline at end of file From 184b7799ea464c5b2b19e55669cf35776b7b8e00 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 11:21:21 +0100 Subject: [PATCH 074/109] use README.md for dry-running gpg --- .github/workflows/appimage.yml | 8 +++++--- .github/workflows/debian.yml | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 3e3e33e34..ce0739e8b 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -16,7 +16,8 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: actions/setup-java@v2 + - name: Setup Java + uses: actions/setup-java@v2 with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} @@ -35,7 +36,8 @@ jobs: echo "::set-output name=semVerStr::${SEM_VER_STR}" echo "::set-output name=semVerNum::${SEM_VER_NUM}" echo "::set-output name=revNum::${REVCOUNT}" - - uses: skymatic/semver-validation-action@v1 + - name: Validate Version + uses: skymatic/semver-validation-action@v1 with: version: ${{ steps.versions.outputs.semVerStr }} - name: Run maven @@ -115,7 +117,7 @@ jobs: - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 3d1cfcdc2..697d93512 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -21,7 +21,8 @@ jobs: run: | sudo apt-get update sudo apt-get install debhelper devscripts dput - - uses: actions/setup-java@v2 + - name: Setup Java + uses: actions/setup-java@v2 with: distribution: 'temurin' java-version: ${{ env.JAVA_VERSION }} @@ -41,12 +42,13 @@ jobs: echo "::set-output name=semVerNum::${SEM_VER_NUM}" echo "::set-output name=revNum::${REVCOUNT}" echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}" - - uses: skymatic/semver-validation-action@v1 + - name: Validate Version + uses: skymatic/semver-validation-action@v1 with: version: ${{ steps.versions.outputs.semVerStr }} - name: Run maven run: mvn -B clean package -Pdependency-check,linux -DskipTests - - name: create orig.tar.gz with common/ libs/ mods/ + - name: Create orig.tar.gz with common/ libs/ mods/ run: | mkdir pkgdir cp -r target/libs pkgdir @@ -70,7 +72,7 @@ jobs: - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} From f43b033ac1fb3b46ef6dff78b2d63f846fb7dc22 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 12:02:41 +0100 Subject: [PATCH 075/109] remove debug options [ci skip] --- .github/workflows/debian.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 697d93512..86a90723d 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -1,7 +1,6 @@ name: Build Debian Package on: - push: # TODO remove before merging into develop release: types: [published] workflow_dispatch: @@ -100,9 +99,9 @@ jobs: cryptomator_*_amd64.deb cryptomator_*.asc - name: Run dput to beta repo - # if: startsWith(github.ref, 'refs/tags/') - run: dput --check-only ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes - - name: Publish AppImage on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes + - name: Publish Debian package on GitHub Releases if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v1 with: From 709d211928cf9452e56931facb4fd28a84324250 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 12:20:43 +0100 Subject: [PATCH 076/109] separate workflow for building .dmg --- .github/workflows/mac-dmg.yml | 236 ++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 .github/workflows/mac-dmg.yml diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml new file mode 100644 index 000000000..c43f9aad7 --- /dev/null +++ b/.github/workflows/mac-dmg.yml @@ -0,0 +1,236 @@ +name: Build macOS .dmg + +on: + push: # TODO remove before merging into develop + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + +jobs: + build: + name: Build Cryptomator.app + runs-on: macos-11 + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,linux -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/linux/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dapple.awt.enableTemplateImages=true" + --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" + --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.showTrayIcon=true" + --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\"" + --mac-package-identifier org.cryptomator + --resource-dir dist/mac/resources + - name: Patch Cryptomator.app + run: | + mv appdir/Cryptomator.app Cryptomator.app + mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ + sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist + sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist + env: + VERSION_NO: ${{ steps.versions.outputs.semVerNum }} + REVISION_NO: ${{ steps.versions.outputs.revNum }} + - name: Install codesign certificate + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH + + # create temporary keychain + security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + security set-keychain-settings -lut 900 $KEYCHAIN_PATH + security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + env: + CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }} + CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }} + CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }} + - name: Codesign + run: | + find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do + if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then + JAR_FILENAME=$(basename ${JAR_PATH}) + OUTPUT_PATH=${JAR_PATH%.*} + echo "Codesigning libs in ${JAR_FILENAME}..." + unzip -q ${JAR_PATH} -d ${OUTPUT_PATH} + find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; + rm ${JAR_PATH} + pushd ${OUTPUT_PATH} > /dev/null + zip -qr ../${JAR_FILENAME} * + popd > /dev/null + rm -r ${OUTPUT_PATH} + fi + done + echo "Codesigning Cryptomator.app..." + codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app + env: + CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} + - name: Prepare .dmg contents + run: | + mkdir dmg + mv Cryptomator.app dmg + cp dist/mac/dmg/resources/macFUSE.webloc dmg + ls -l dmg + - name: Install create-dmg + run: | + brew install create-dmg + create-dmg --help + - name: Create .dmg + run: > + create-dmg + --volname Cryptomator + --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns" + --background "dist/mac/dmg/resources/Cryptomator-background.tiff" + --window-pos 400 100 + --window-size 640 694 + --icon-size 128 + --icon "Cryptomator.app" 128 245 + --hide-extension "Cryptomator.app" + --icon "macFUSE.webloc" 320 501 + --hide-extension "macFUSE.webloc" + --app-drop-link 512 245 + --eula "dist/mac/dmg/resources/license.rtf" + --icon ".background" 128 758 + --icon ".fseventsd" 320 758 + --icon ".VolumeIcon.icns" 512 758 + Cryptomator-${VERSION_NO}.dmg dmg + env: + VERSION_NO: ${{ steps.versions.outputs.semVerNum }} + - name: Install notarization credentials + if: startsWith(github.ref, 'refs/tags/') + run: | + # create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db + security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} + security set-keychain-settings -lut 900 ${KEYCHAIN_PATH} + security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} + + # import credentials from secrets + sudo xcode-select -s /Applications/Xcode_13.0.app + xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}" + env: + NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} + NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} + NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }} + NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }} + - name: Notarize .dmg + if: startsWith(github.ref, 'refs/tags/') + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db + sudo xcode-select -s /Applications/Xcode_13.0.app + xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait + xcrun stapler staple Cryptomator-*.dmg + env: + NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} + - name: Add possible alpha/beta tags to installer name + run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg + - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Create detached GPG signatures + run: | + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.dmg + - name: Clean up codesign certificate + if: ${{ always() }} + run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db + continue-on-error: true + - name: Clean up notarization credentials + if: ${{ always() }} + run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db + continue-on-error: true + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: dmg + path: Cryptomator-*.dmg + if-no-files-found: error + - name: Publish dmg on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + *.dmg + *.asc + + From c8651d93752bfb3238df7349049c68ff7ff8f733 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 12:29:45 +0100 Subject: [PATCH 077/109] only use three version numbers for mac apps --- .github/workflows/mac-dmg.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index c43f9aad7..a35004430 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -73,7 +73,7 @@ jobs: --name Cryptomator --vendor "Skymatic GmbH" --copyright "(C) 2016 - 2022 Skymatic GmbH" - --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --app-version "${{ steps.versions.outputs.semVerNum }}" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" From be448128960adfe811528a05118a71058400be38 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 12:36:13 +0100 Subject: [PATCH 078/109] fix copy-paste errors --- .github/workflows/mac-dmg.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index a35004430..ab643c4f9 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -42,11 +42,11 @@ jobs: with: version: ${{ steps.versions.outputs.semVerStr }} - name: Run maven - run: mvn -B clean package -Pdependency-check,linux -DskipTests + run: mvn -B clean package -Pdependency-check,mac -DskipTests - name: Patch target dir run: | cp LICENSE.txt target - cp dist/linux/launcher.sh target + cp dist/mac/launcher.sh target cp target/cryptomator-*.jar target/mods - name: Run jlink run: > @@ -208,7 +208,7 @@ jobs: GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Create detached GPG signatures run: | - gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.dmg + gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg - name: Clean up codesign certificate if: ${{ always() }} run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db From c7f64f49742a7b539e3c65fe7430ddfd8a161c30 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 12:59:56 +0100 Subject: [PATCH 079/109] remove debug options [ci skip] --- .github/workflows/mac-dmg.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index ab643c4f9..fc4e337fa 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -1,7 +1,6 @@ name: Build macOS .dmg on: - push: # TODO remove before merging into develop release: types: [published] workflow_dispatch: @@ -73,10 +72,10 @@ jobs: --name Cryptomator --vendor "Skymatic GmbH" --copyright "(C) 2016 - 2022 Skymatic GmbH" - --app-version "${{ steps.versions.outputs.semVerNum }}" + --app-version "${{ steps.versions.outputs.semVerNum }}" --java-options "-Xss5m" --java-options "-Xmx256m" - --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" + --java-options "-Dcryptomator.appVersion=\"${{ steps.versions.outputs.semVerStr }}\"" --java-options "-Dfile.encoding=\"utf-8\"" --java-options "-Dapple.awt.enableTemplateImages=true" --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" @@ -84,7 +83,7 @@ jobs: --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.showTrayIcon=true" - --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\"" + --java-options "-Dcryptomator.buildNumber=\"dmg-${{ steps.versions.outputs.revNum }}\"" --mac-package-identifier org.cryptomator --resource-dir dist/mac/resources - name: Patch Cryptomator.app @@ -169,7 +168,7 @@ jobs: --icon ".VolumeIcon.icns" 512 758 Cryptomator-${VERSION_NO}.dmg dmg env: - VERSION_NO: ${{ steps.versions.outputs.semVerNum }} + VERSION_NO: ${{ steps.versions.outputs.semVerNum }} - name: Install notarization credentials if: startsWith(github.ref, 'refs/tags/') run: | @@ -198,17 +197,14 @@ jobs: env: NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - name: Add possible alpha/beta tags to installer name - run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg - - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 + run: mv Cryptomator-*.dmg Cryptomator-${{ steps.versions.outputs.semVerStr }}.dmg + - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign README.md + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg env: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: Create detached GPG signatures - run: | - gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.dmg - name: Clean up codesign certificate if: ${{ always() }} run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db From 6cd349523b374aeec475809eb18a638b8689529e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 13:00:14 +0100 Subject: [PATCH 080/109] separate workflow for building .exe --- .github/workflows/win-exe.yml | 256 ++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 .github/workflows/win-exe.yml diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml new file mode 100644 index 000000000..1cb85bfca --- /dev/null +++ b/.github/workflows/win-exe.yml @@ -0,0 +1,256 @@ +name: Build Windows Installer + +on: + push: # TODO remove before merging into develop + release: + types: [published] + workflow_dispatch: + +env: + JAVA_VERSION: 17 + WINFSP_MSI: https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi + +defaults: + run: + shell: bash + +jobs: + build-msi: + name: Build .msi Installer + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - id: versions + name: Apply version information + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + SEM_VER_STR=${GITHUB_REF##*/} + mvn versions:set -DnewVersion=${SEM_VER_STR} + else + SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` + fi + SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` + REVCOUNT=`git rev-list --count HEAD` + echo "::set-output name=semVerStr::${SEM_VER_STR}" + echo "::set-output name=semVerNum::${SEM_VER_NUM}" + echo "::set-output name=revNum::${REVCOUNT}" + - name: Validate Version + uses: skymatic/semver-validation-action@v1 + with: + version: ${{ steps.versions.outputs.semVerStr }} + - name: Run maven + run: mvn -B clean package -Pdependency-check,win -DskipTests + - name: Patch target dir + run: | + cp LICENSE.txt target + cp dist/linux/launcher.sh target + cp target/cryptomator-*.jar target/mods + - name: Run jlink + run: > + ${JAVA_HOME}/bin/jlink + --verbose + --output runtime + --module-path "${JAVA_HOME}/jmods" + --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr + --strip-native-commands + --no-header-files + --no-man-pages + --strip-debug + --compress=1 + - name: Run jpackage + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type app-image + --runtime-image runtime + --input target/libs + --module-path target/mods + --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator + --dest appdir + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}.${{ steps.versions.outputs.revNum }}" + --java-options "-Xss5m" + --java-options "-Xmx256m" + --java-options "-Dfile.encoding=\"utf-8\"" + --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\"" + --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\"" + --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\"" + --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\"" + --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\"" + --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" + --java-options "-Dcryptomator.showTrayIcon=true" + --java-options "-Dcryptomator.buildNumber=\"msi-${{ steps.versions.outputs.revNum }}\"" + --resource-dir dist/win/resources + --icon dist/win/resources/Cryptomator.ico + - name: Patch Application Directory + run: | + cp dist/win/contrib/* appdir/Cryptomator + - name: Fix permissions + run: attrib -r appdir/Cryptomator/Cryptomator.exe + shell: pwsh + - name: Codesign + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator + timestampUrl: 'http://timestamp.digicert.com' + folder: appdir/Cryptomator + recursive: true + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/resources" + - name: Create MSI + run: > + ${JAVA_HOME}/bin/jpackage + --verbose + --type msi + --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 + --app-image appdir/Cryptomator + --dest installer + --name Cryptomator + --vendor "Skymatic GmbH" + --copyright "(C) 2016 - 2022 Skymatic GmbH" + --app-version "${{ steps.versions.outputs.semVerNum }}" + --win-menu + --win-dir-chooser + --win-shortcut-prompt + --win-update-url "https:\\cryptomator.org" + --win-menu-group Cryptomator + --resource-dir dist/win/resources + --license-file dist/win/resources/license.rtf + --file-associations dist/win/resources/FAvaultFile.properties + env: + JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs + - name: Codesign MSI + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: installer + - name: Add possible alpha/beta tags to installer name + run: mv installer/Cryptomator-*.msi Cryptomator-${{ steps.versions.outputs.semVerStr }}-x64.msi + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.msi + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: msi + path: | + Cryptomator-*.msi + Cryptomator-*.asc + if-no-files-found: error + - name: Publish .msi on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + *.msi + *.asc + outputs: + semVerNum: ${{ steps.versions.outputs.semVerNum }} + semVerStr: ${{ steps.versions.outputs.semVerStr }} + revNum: ${{ steps.versions.outputs.revNum }} + + build-exe: + name: Build .exe installer + runs-on: windows-latest + needs: [build-msi] + steps: + - uses: actions/checkout@v2 + - name: Download .msi + uses: actions/download-artifact@v2 + with: + name: msi + path: dist/win/bundle/resources + - name: Strip version info from msi file name + run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JAVA_VERSION }} + cache: 'maven' + - name: Generate license + run: > + mvn -B license:add-third-party + "-Dlicense.thirdPartyFilename=license.rtf" + "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" + "-Dlicense.outputDirectory=dist/win/bundle/resources" + - name: Download WinFsp + run: + curl --output dist/win/bundle/resources/winfsp.msi -L ${{ env.WINFSP_MSI }} + - name: Compile to wixObj file + run: > + "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs + -ext WixBalExtension + -out dist/win/bundle/ + -dBundleVersion="${{ needs.build-exe.outputs.semVerNum }}.${{ needs.build-exe.outputs.revNum }}" + -dBundleVendor="Skymatic GmbH" + -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH" + -dAboutUrl="https://cryptomator.org" + -dHelpUrl="https://cryptomator.org/contact" + -dUpdateUrl="https://cryptomator.org/downloads/" + - name: Create executable with linker + run: > + "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj + -ext WixBalExtension + -out installer/Cryptomator.exe + - name: Codesign EXE + uses: skymatic/code-sign-action@v1 + with: + certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} + password: ${{ secrets.WIN_CODESIGN_P12_PW }} + certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B + description: Cryptomator Installer + timestampUrl: 'http://timestamp.digicert.com' + folder: installer + - name: Add possible alpha/beta tags to installer name + run: mv installer/Cryptomator.exe Cryptomator-${{ needs.build-exe.outputs.semVerStr }}-x64.exe + - name: Create detached GPG signature with key 615D449FE6E6A235 + run: | + echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import + echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a Cryptomator-*.exe + env: + GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: exe + path: | + Cryptomator-*.exe + Cryptomator-*.asc + if-no-files-found: error + - name: Publish .msi on GitHub Releases + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files: true + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + files: | + *.exe + *.asc \ No newline at end of file From 0443bfb0a2099e2feffa94c380090072fbed8c67 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 13:14:18 +0100 Subject: [PATCH 081/109] wrong job name in expression --- .github/workflows/win-exe.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 1cb85bfca..5be12cbe4 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -208,7 +208,7 @@ jobs: "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs -ext WixBalExtension -out dist/win/bundle/ - -dBundleVersion="${{ needs.build-exe.outputs.semVerNum }}.${{ needs.build-exe.outputs.revNum }}" + -dBundleVersion="${{ needs.build-msi.outputs.semVerNum }}.${{ needs.build-msi.outputs.revNum }}" -dBundleVendor="Skymatic GmbH" -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH" -dAboutUrl="https://cryptomator.org" @@ -229,7 +229,7 @@ jobs: timestampUrl: 'http://timestamp.digicert.com' folder: installer - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator.exe Cryptomator-${{ needs.build-exe.outputs.semVerStr }}-x64.exe + run: mv installer/Cryptomator.exe Cryptomator-${{ needs.build-msi.outputs.semVerStr }}-x64.exe - name: Create detached GPG signature with key 615D449FE6E6A235 run: | echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import From e0b3525504a6d1de885581d32948f94b33930efe Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:01:06 +0100 Subject: [PATCH 082/109] bumped actions/upload-artifact version --- .github/workflows/appimage.yml | 2 +- .github/workflows/debian.yml | 2 +- .github/workflows/mac-dmg.yml | 2 +- .github/workflows/win-exe.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index ce0739e8b..ef06ef1e2 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -131,7 +131,7 @@ jobs: gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: appimage path: | diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 86a90723d..807e833a1 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -87,7 +87,7 @@ jobs: run: | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: linux-deb-package path: | diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index fc4e337fa..0c4043208 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -214,7 +214,7 @@ jobs: run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db continue-on-error: true - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: dmg path: Cryptomator-*.dmg diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 5be12cbe4..853dcf789 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -155,7 +155,7 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: msi path: | @@ -238,7 +238,7 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: exe path: | From 5f17a666324e9409fbbcc59533d89abde079cb0b Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:02:00 +0100 Subject: [PATCH 083/109] draft a release for tagged builds if build succeeds --- .github/workflows/build.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 750b64826..40d2b2e1b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,4 +48,16 @@ jobs: run: bash <(curl -Ls https://coverage.codacy.com/get.sh) env: CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} - continue-on-error: true \ No newline at end of file + continue-on-error: true + - name: Draft a release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + draft: true + discussion_category_name: releases + token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} + generate_release_notes: true + body: |- + :construction: Work in Progress + + --- From 88db74d595bd6bdbaff4e1f8b03738307edc6e89 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:03:07 +0100 Subject: [PATCH 084/109] remove old release workflow --- .github/workflows/release.yml | 738 ---------------------------------- 1 file changed, 738 deletions(-) delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index a24342da3..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,738 +0,0 @@ -name: Installers and Release - -on: - workflow_dispatch: - inputs: - semver: - description: 'SemVer' - required: true - default: '0.99.99-SNAPSHOT' - push: - tags: # see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet - - '[0-9]+.[0-9]+.[0-9]+' - - '[0-9]+.[0-9]+.[0-9]+-*' - -env: - JAVA_VERSION: 17 - -defaults: - run: - shell: bash - -jobs: - -# -# Buildkit -# - buildkit: - name: Build ${{ matrix.profile }}-buildkit - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - include: - - os: ubuntu-latest - profile: linux - - os: windows-latest - profile: win - - os: macos-latest - profile: mac - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - cache: 'maven' - - name: Ensure to use tagged version - run: mvn versions:set -DnewVersion=${GITHUB_REF##*/} # use shell parameter expansion to strip of 'refs/tags' - if: startsWith(github.ref, 'refs/tags/') - - name: Build and Test - run: mvn -B clean package -Pdependency-check,${{ matrix.profile }} - - name: Patch buildkit - run: | - cp LICENSE.txt target - cp dist/${{ matrix.profile }}/launcher* target - cp target/cryptomator-*.jar target/mods - - name: Upload ${{ matrix.profile }}-buildkit - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.profile }}-buildkit - path: | - target/libs - target/mods - target/LICENSE.txt - target/launcher* - if-no-files-found: error - -# -# Release Metadata -# - metadata: - name: Determine Version Metadata - runs-on: ubuntu-latest - outputs: - semVerNum: ${{ steps.versions.outputs.semVerNum }} - semVerStr: ${{ steps.versions.outputs.semVerStr }} - ppaVerStr: ${{ steps.versions.outputs.ppaVerStr }} - revNum: ${{ steps.versions.outputs.revNum }} - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - id: versions - run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then - SEM_VER_STR=${GITHUB_REF##*/} - else - SEM_VER_STR=${{ github.event.inputs.semver }} - fi - SEM_VER_NUM=`echo ${SEM_VER_STR} | sed -E 's/([0-9]+\.[0-9]+\.[0-9]+).*/\1/'` - REVCOUNT=`git rev-list --count HEAD` - echo "::set-output name=semVerStr::${SEM_VER_STR}" - echo "::set-output name=semVerNum::${SEM_VER_NUM}" - echo "::set-output name=ppaVerStr::${SEM_VER_STR/-/\~}-${REVCOUNT}" - echo "::set-output name=revNum::${REVCOUNT}" - - uses: skymatic/semver-validation-action@v1 - with: - version: ${{ steps.versions.outputs.semVerStr }} - -# -# Application Directory -# - appdir: - name: Create ${{ matrix.profile }}-appdir - needs: [buildkit, metadata] - runs-on: ${{ matrix.os }} - strategy: - fail-fast: true - matrix: - include: - - os: ubuntu-latest - profile: linux - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dcryptomator.logDir=\"~/.local/share/Cryptomator/logs\"" - --java-options "-Dcryptomator.pluginDir=\"~/.local/share/Cryptomator/plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/.config/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.mountPointsDir=\"~/.local/share/Cryptomator/mnt\"" - --java-options "-Dcryptomator.showTrayIcon=false" - --java-options "-Dcryptomator.buildNumber=\"appimage-${{ needs.metadata.outputs.revNum }}\"" - --resource-dir dist/linux/resources - - os: windows-latest - profile: win - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dcryptomator.logDir=\"~/AppData/Roaming/Cryptomator\"" - --java-options "-Dcryptomator.pluginDir=\"~/AppData/Roaming/Cryptomator/Plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/AppData/Roaming/Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/AppData/Roaming/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.keychainPath=\"~/AppData/Roaming/Cryptomator/keychain.json\"" - --java-options "-Dcryptomator.mountPointsDir=\"~/Cryptomator\"" - --java-options "-Dcryptomator.showTrayIcon=true" - --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.metadata.outputs.revNum }}\"" - --resource-dir dist/win/resources - --icon dist/win/resources/Cryptomator.ico - - os: macos-latest - profile: mac - jpackageoptions: > - --app-version "${{ needs.metadata.outputs.semVerNum }}" - --java-options "-Dfile.encoding=\"utf-8\"" - --java-options "-Dapple.awt.enableTemplateImages=true" - --java-options "-Dcryptomator.logDir=\"~/Library/Logs/Cryptomator\"" - --java-options "-Dcryptomator.pluginDir=\"~/Library/Application Support/Cryptomator/Plugins\"" - --java-options "-Dcryptomator.settingsPath=\"~/Library/Application Support/Cryptomator/settings.json\"" - --java-options "-Dcryptomator.ipcSocketPath=\"~/Library/Application Support/Cryptomator/ipc.socket\"" - --java-options "-Dcryptomator.showTrayIcon=true" - --java-options "-Dcryptomator.buildNumber=\"dmg-${{ needs.metadata.outputs.revNum }}\"" - --mac-package-identifier org.cryptomator - --resource-dir dist/mac/resources - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - - name: Download ${{ matrix.profile }}-buildkit - uses: actions/download-artifact@v2 - with: - name: ${{ matrix.profile }}-buildkit - path: buildkit - - name: Create Runtime Image - run: > - ${JAVA_HOME}/bin/jlink - --verbose - --output runtime - --module-path "${JAVA_HOME}/jmods" - --add-modules java.base,java.desktop,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.crypto.ec,jdk.accessibility,jdk.management.jfr - --no-header-files - --no-man-pages - --strip-debug - --compress=1 - - name: Create App Directory - run: > - ${JAVA_HOME}/bin/jpackage - --verbose - --type app-image - --runtime-image runtime - --input buildkit/libs - --module-path buildkit/mods - --module org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator - --dest appdir - --name Cryptomator - --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2022 Skymatic GmbH" - --java-options "-Xss5m" - --java-options "-Xmx256m" - --java-options "-Dcryptomator.appVersion=\"${{ needs.metadata.outputs.semVerStr }}\"" - ${{ matrix.jpackageoptions }} - - name: Create appdir.tar - run: tar -cvf appdir.tar appdir - - name: Upload ${{ matrix.profile }}-appdir - uses: actions/upload-artifact@v2 - with: - name: ${{ matrix.profile }}-appdir - path: appdir.tar - if-no-files-found: error - -# -# Debian Package -# - deb: - name: Create Debian Package - needs: [buildkit, metadata] - runs-on: ubuntu-18.04 - steps: - - uses: actions/checkout@v2 - - name: install build tools - run: | - sudo apt-get update - sudo apt-get install debhelper devscripts - - name: Download linux-buildkit - uses: actions/download-artifact@v2 - with: - name: linux-buildkit - path: pkgdir - - name: create orig.tar.gz with common/ libs/ mods/ - run: | - cp -r dist/linux/common/ pkgdir - tar -cJf cryptomator_${{ needs.metadata.outputs.ppaVerStr }}.orig.tar.xz -C pkgdir . - - name: patch and rename pkgdir - run: | - cp -r dist/linux/debian/ pkgdir - export RFC2822_TIMESTAMP=`date --rfc-2822` - envsubst '${SEMVER_STR} ${VERSION_NUM} ${REVISION_NUM}' < dist/linux/debian/rules > pkgdir/debian/rules - envsubst '${PPA_VERSION} ${RFC2822_TIMESTAMP}' < dist/linux/debian/changelog > pkgdir/debian/changelog - find . -name "*.jar" >> pkgdir/debian/source/include-binaries - mv pkgdir cryptomator_${{ needs.metadata.outputs.ppaVerStr }} - env: - SEMVER_STR: ${{ needs.metadata.outputs.semVerStr }} - VERSION_NUM: ${{ needs.metadata.outputs.semVerNum }} - REVISION_NUM: ${{ needs.metadata.outputs.revNum }} - PPA_VERSION: ${{ needs.metadata.outputs.ppaVerStr }}-0ppa1 - - name: import gpg key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign dist/linux/debian/rules - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: debuild - run: | - debuild -S -sa -d - debuild -b -sa -d - env: - DEBSIGN_PROGRAM: gpg --batch --pinentry-mode loopback - DEBSIGN_KEYID: 615D449FE6E6A235 - working-directory: cryptomator_${{ needs.metadata.outputs.ppaVerStr }} - - name: Upload artifacts - uses: actions/upload-artifact@v2 - with: - name: linux-deb-package - path: | - cryptomator_*.dsc - cryptomator_*.orig.tar.xz - cryptomator_*.debian.tar.xz - cryptomator_*_source.buildinfo - cryptomator_*_source.changes - cryptomator_*_amd64.deb - -# -# Upload Source Package to PPA -# - ppa: - name: Upload Source Package to PPA - needs: [deb] - runs-on: ubuntu-18.04 - steps: - - name: install dput - run: | - sudo apt-get update - sudo apt-get install dput - - name: import public key - run: curl -sSL https://github.com/cryptobot.gpg | gpg --import - - - name: download linux-deb-package - uses: actions/download-artifact@v2 - with: - name: linux-deb-package - path: . - - name: dput to beta repo - run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes - -# -# Linux Cryptomator.AppImage -# - linux-appimage: - name: Build Cryptomator.AppImage - runs-on: ubuntu-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download linux-appdir - uses: actions/download-artifact@v2 - with: - name: linux-appdir - - name: Untar appdir.tar - run: | - tar -xvf appdir.tar - - name: Patch Cryptomator.AppDir - run: | - mv appdir/Cryptomator Cryptomator.AppDir - cp -r dist/linux/appimage/resources/AppDir/* Cryptomator.AppDir/ - envsubst '${REVISION_NO} ${SEMVER_STR}' < dist/linux/appimage/resources/AppDir/bin/cryptomator.sh > Cryptomator.AppDir/bin/cryptomator.sh - cp dist/linux/common/org.cryptomator.Cryptomator256.png Cryptomator.AppDir/usr/share/icons/hicolor/256x256/apps/org.cryptomator.Cryptomator.png - cp dist/linux/common/org.cryptomator.Cryptomator512.png Cryptomator.AppDir/usr/share/icons/hicolor/512x512/apps/org.cryptomator.Cryptomator.png - cp dist/linux/common/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg - cp dist/linux/common/org.cryptomator.Cryptomator.metainfo.xml Cryptomator.AppDir/usr/share/metainfo/org.cryptomator.Cryptomator.metainfo.xml - cp dist/linux/common/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/usr/share/applications/org.cryptomator.Cryptomator.desktop - cp dist/linux/common/application-vnd.cryptomator.vault.xml Cryptomator.AppDir/usr/share/mime/packages/application-vnd.cryptomator.vault.xml - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/org.cryptomator.Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/Cryptomator.svg - ln -s usr/share/icons/hicolor/scalable/apps/org.cryptomator.Cryptomator.svg Cryptomator.AppDir/.DirIcon - ln -s usr/share/applications/org.cryptomator.Cryptomator.desktop Cryptomator.AppDir/Cryptomator.desktop - ln -s bin/cryptomator.sh Cryptomator.AppDir/AppRun - env: - REVISION_NO: ${{ needs.metadata.outputs.revNum }} - SEMVER_STR: ${{ needs.metadata.outputs.semVerStr }} - - name: Extract libjffi.so # workaround for https://github.com/cryptomator/cryptomator-linux/issues/27 - run: | - JFFI_NATIVE_JAR=`ls lib/app/ | grep -e 'jffi-[1-9]\.[0-9]\{1,2\}.[0-9]\{1,2\}-native.jar'` - ${JAVA_HOME}/bin/jar -xf lib/app/${JFFI_NATIVE_JAR} /jni/x86_64-Linux/ - mv jni/x86_64-Linux/* lib/app/libjffi.so - working-directory: Cryptomator.AppDir - - name: Download AppImageKit - run: | - curl -L https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -o appimagetool.AppImage - chmod +x appimagetool.AppImage - ./appimagetool.AppImage --appimage-extract - - name: Prepare GPG-Agent for signing with key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --dry-run --sign Cryptomator.AppDir/AppRun - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: Build AppImage - run: > - ./squashfs-root/AppRun Cryptomator.AppDir cryptomator-${{ needs.metadata.outputs.semVerStr }}-x86_64.AppImage - -u 'gh-releases-zsync|cryptomator|cryptomator|latest|cryptomator-*-x86_64.AppImage.zsync' - --sign --sign-key=615D449FE6E6A235 --sign-args="--batch --pinentry-mode loopback" - - name: Upload AppImage - uses: actions/upload-artifact@v2 - with: - name: linux-appimage - path: | - cryptomator-*.AppImage - cryptomator-*.AppImage.zsync - if-no-files-found: error - -# -# macOS Cryptomator.app -# - mac-app: - name: Build Cryptomator.app - runs-on: macos-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download mac-appdir - uses: actions/download-artifact@v2 - with: - name: mac-appdir - - name: Untar appdir.tar - run: tar -xvf appdir.tar - - name: Patch Cryptomator.app - run: | - mv appdir/Cryptomator.app Cryptomator.app - mv dist/mac/resources/Cryptomator-Vault.icns Cryptomator.app/Contents/Resources/ - sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" Cryptomator.app/Contents/Info.plist - sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" Cryptomator.app/Contents/Info.plist - env: - VERSION_NO: ${{ needs.metadata.outputs.semVerNum }} - REVISION_NO: ${{ needs.metadata.outputs.revNum }} - - name: Install codesign certificate - env: - CODESIGN_P12_BASE64: ${{ secrets.MACOS_CODESIGN_P12_BASE64 }} - CODESIGN_P12_PW: ${{ secrets.MACOS_CODESIGN_P12_PW }} - CODESIGN_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_CODESIGN_TMP_KEYCHAIN_PW }} - run: | - # create variables - CERTIFICATE_PATH=$RUNNER_TEMP/codesign.p12 - KEYCHAIN_PATH=$RUNNER_TEMP/codesign.keychain-db - - # import certificate and provisioning profile from secrets - echo -n "$CODESIGN_P12_BASE64" | base64 --decode --output $CERTIFICATE_PATH - - # create temporary keychain - security create-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH - security set-keychain-settings -lut 900 $KEYCHAIN_PATH - security unlock-keychain -p "$CODESIGN_TMP_KEYCHAIN_PW" $KEYCHAIN_PATH - - # import certificate to keychain - security import $CERTIFICATE_PATH -P "$CODESIGN_P12_PW" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH - security list-keychain -d user -s $KEYCHAIN_PATH - - name: Codesign - env: - CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} - run: | - find Cryptomator.app/Contents/runtime/Contents/MacOS -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - for JAR_PATH in `find Cryptomator.app -name "*.jar"`; do - if [[ `unzip -l ${JAR_PATH} | grep '.dylib\|.jnilib'` ]]; then - JAR_FILENAME=$(basename ${JAR_PATH}) - OUTPUT_PATH=${JAR_PATH%.*} - echo "Codesigning libs in ${JAR_FILENAME}..." - unzip -q ${JAR_PATH} -d ${OUTPUT_PATH} - find ${OUTPUT_PATH} -name '*.dylib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - find ${OUTPUT_PATH} -name '*.jnilib' -exec codesign --force -s ${CODESIGN_IDENTITY} {} \; - rm ${JAR_PATH} - pushd ${OUTPUT_PATH} > /dev/null - zip -qr ../${JAR_FILENAME} * - popd > /dev/null - rm -r ${OUTPUT_PATH} - fi - done - echo "Codesigning Cryptomator.app..." - codesign --force --deep --entitlements dist/mac/Cryptomator.entitlements -o runtime -s ${CODESIGN_IDENTITY} Cryptomator.app - - name: Clean up codesign certificate - if: ${{ always() }} - run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db - - name: Create app.tar - run: tar -cvf app.tar Cryptomator.app - - name: Upload mac-app - uses: actions/upload-artifact@v2 - with: - name: mac-app - path: app.tar - if-no-files-found: error - -# -# macOS Cryptomator.dmg -# - mac-dmg: - name: Build Cryptomator.dmg - runs-on: macos-11 - needs: [mac-app, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download mac-appdir - uses: actions/download-artifact@v2 - with: - name: mac-app - - name: Untar app.tar - run: tar -xvf app.tar - - name: Prepare .dmg contents - run: | - mkdir dmg - mv Cryptomator.app dmg - cp dist/mac/dmg/resources/macFUSE.webloc dmg - ls -l dmg - - name: Install create-dmg - run: | - brew install create-dmg - create-dmg --help - - name: Create .dmg - run: > - create-dmg - --volname Cryptomator - --volicon "dist/mac/dmg/resources/Cryptomator-Volume.icns" - --background "dist/mac/dmg/resources/Cryptomator-background.tiff" - --window-pos 400 100 - --window-size 640 694 - --icon-size 128 - --icon "Cryptomator.app" 128 245 - --hide-extension "Cryptomator.app" - --icon "macFUSE.webloc" 320 501 - --hide-extension "macFUSE.webloc" - --app-drop-link 512 245 - --eula "dist/mac/dmg/resources/license.rtf" - --icon ".background" 128 758 - --icon ".fseventsd" 320 758 - --icon ".VolumeIcon.icns" 512 758 - Cryptomator-${VERSION_NO}.dmg dmg - env: - VERSION_NO: ${{ needs.metadata.outputs.semVerNum }} - - name: Install notarization credentials - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} - NOTARIZATION_PW: ${{ secrets.MACOS_NOTARIZATION_PW }} - NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} - NOTARIZATION_TMP_KEYCHAIN_PW: ${{ secrets.MACOS_NOTARIZATION_TMP_KEYCHAIN_PW }} - run: | - # create temporary keychain - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - security create-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - security set-keychain-settings -lut 900 ${KEYCHAIN_PATH} - security unlock-keychain -p "${NOTARIZATION_TMP_KEYCHAIN_PW}" ${KEYCHAIN_PATH} - - # import credentials from secrets - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool store-credentials "${NOTARIZATION_KEYCHAIN_PROFILE}" --apple-id "${NOTARIZATION_APPLE_ID}" --password "${NOTARIZATION_PW}" --team-id "${NOTARIZATION_TEAM_ID}" --keychain "${KEYCHAIN_PATH}" - - name: Notarize .dmg - env: - NOTARIZATION_KEYCHAIN_PROFILE: ${{ secrets.MACOS_NOTARIZATION_KEYCHAIN_PROFILE }} - run: | - KEYCHAIN_PATH=$RUNNER_TEMP/notarization.keychain-db - sudo xcode-select -s /Applications/Xcode_13.0.app - xcrun notarytool submit Cryptomator-*.dmg --keychain-profile "${NOTARIZATION_KEYCHAIN_PROFILE}" --keychain "${KEYCHAIN_PATH}" --wait - xcrun stapler staple Cryptomator-*.dmg - - name: Clean up notarization credentials - if: ${{ always() }} - run: security delete-keychain $RUNNER_TEMP/notarization.keychain-db - - name: Add possible alpha/beta tags to installer name - run: mv Cryptomator-*.dmg Cryptomator-${{ needs.metadata.outputs.semVerStr }}.dmg - - name: Upload mac-dmg - uses: actions/upload-artifact@v2 - with: - name: mac-dmg - path: Cryptomator-*.dmg - if-no-files-found: error - -# -# MSI package -# - win-msi: - name: Build Cryptomator.msi - runs-on: windows-latest - needs: [appdir, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download win-appdir - uses: actions/download-artifact@v2 - with: - name: win-appdir - - name: Untar appdir.tar - run: tar -xvf appdir.tar - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - cache: 'maven' - - name: Patch Application Directory - run: | - cp dist/win/contrib/* appdir/Cryptomator - - name: Fix permissions - run: attrib -r appdir/Cryptomator/Cryptomator.exe - shell: pwsh - - name: Codesign - uses: skymatic/code-sign-action@v1 - with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B - description: Cryptomator - timestampUrl: 'http://timestamp.digicert.com' - folder: appdir/Cryptomator - recursive: true - - name: Generate license - run: > - mvn -B license:add-third-party - "-Dlicense.thirdPartyFilename=license.rtf" - "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" - "-Dlicense.outputDirectory=dist/win/resources" - - name: Create MSI - run: > - ${JAVA_HOME}/bin/jpackage - --verbose - --type msi - --win-upgrade-uuid bda45523-42b1-4cae-9354-a45475ed4775 - --app-image appdir/Cryptomator - --dest installer - --name Cryptomator - --vendor "Skymatic GmbH" - --copyright "(C) 2016 - 2022 Skymatic GmbH" - --app-version "${{ needs.metadata.outputs.semVerNum }}" - --win-menu - --win-dir-chooser - --win-shortcut-prompt - --win-update-url "https:\\cryptomator.org" - --win-menu-group Cryptomator - --resource-dir dist/win/resources - --license-file dist/win/resources/license.rtf - --file-associations dist/win/resources/FAvaultFile.properties - env: - JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs - - name: Codesign MSI - uses: skymatic/code-sign-action@v1 - with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: installer - - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator-*.msi installer/Cryptomator-${{ needs.metadata.outputs.semVerStr }}-x64.msi - - name: Upload win-msi - uses: actions/upload-artifact@v2 - with: - name: win-msi - path: installer/*.msi - if-no-files-found: error - -# -# Windows Cryptomator.exe bundle -# - win-exe: - name: Build Cryptomator.exe bundle - runs-on: windows-latest - needs: [win-msi, metadata] - steps: - - uses: actions/checkout@v2 - - name: Download Windows msi - uses: actions/download-artifact@v2 - with: - name: win-msi - path: dist/win/bundle/resources - - name: Strip version info from msi file name - run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi - - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: ${{ env.JAVA_VERSION }} - cache: 'maven' - - name: Generate license - run: > - mvn -B license:add-third-party - "-Dlicense.thirdPartyFilename=license.rtf" - "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" - "-Dlicense.outputDirectory=dist/win/bundle/resources" - - name: Download winfsp - run: - curl --output dist/win/bundle/resources/winfsp.msi -L https://github.com/winfsp/winfsp/releases/download/v1.10/winfsp-1.10.22006.msi - - name: Compile to wixObj file - run: > - "${WIX}/bin/candle.exe" dist/win/bundle/bundleWithWinfsp.wxs - -ext WixBalExtension - -out dist/win/bundle/ - -dBundleVersion="${{ needs.metadata.outputs.semVerNum }}.${{ needs.metadata.outputs.revNum }}" - -dBundleVendor="Skymatic GmbH" - -dBundleCopyright="(C) 2016 - 2022 Skymatic GmbH" - -dAboutUrl="https://cryptomator.org" - -dHelpUrl="https://cryptomator.org/contact" - -dUpdateUrl="https://cryptomator.org/downloads/" - - name: Create executable with linker - run: > - "${WIX}/bin/light.exe" -b dist/win/ dist/win/bundle/bundleWithWinfsp.wixobj - -ext WixBalExtension - -out installer/Cryptomator.exe - - name: Codesign EXE - uses: skymatic/code-sign-action@v1 - with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: FF52240075AD7D14AF25629FDF69635357C7D14B - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: installer - - name: Add possible alpha/beta tags to installer name - run: mv installer/Cryptomator.exe installer/Cryptomator-${{ needs.metadata.outputs.semVerStr }}-x64.exe - - name: Upload win-exe - uses: actions/upload-artifact@v2 - with: - name: win-exe - path: installer/*.exe - if-no-files-found: error - -# -# Release -# - release: - name: Draft a release on Github - runs-on: ubuntu-latest - needs: [metadata,linux-appimage,mac-dmg,win-msi,win-exe,ppa] - if: startsWith(github.ref, 'refs/tags/') && github.repository == 'cryptomator/cryptomator' - steps: - - uses: actions/checkout@v2 - - name: Create tarball - run: git archive --prefix="cryptomator-${{ needs.metadata.outputs.semVerStr }}/" -o "cryptomator-${{ needs.metadata.outputs.semVerStr }}.tar.gz" ${{ github.ref }} - - name: Download Debian package - uses: actions/download-artifact@v2 - with: - name: linux-deb-package - - name: Download linux appimage - uses: actions/download-artifact@v2 - with: - name: linux-appimage - - name: Download macOS dmg - uses: actions/download-artifact@v2 - with: - name: mac-dmg - - name: Download Windows msi - uses: actions/download-artifact@v2 - with: - name: win-msi - - name: Download Windows exe - uses: actions/download-artifact@v2 - with: - name: win-exe - - name: Create detached GPG signature for all release files with key 615D449FE6E6A235 - run: | - echo "${GPG_PRIVATE_KEY}" | gpg --batch --quiet --import - for FILE in `find . -name "*.AppImage" -o -name "*.deb" -o -name "*.dmg" -o -name "*.exe" -o -name "*.msi" -o -name "*.zsync" -o -name "*.tar.gz"`; do - echo "${GPG_PASSPHRASE}" | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a ${FILE} - done - env: - GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} - GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - - name: Compute SHA256 checksums of release artifacts - run: | - SHA256_SUMS=`find . -name "*.AppImage" -o -name "*.deb" -o -name "*.dmg" -o -name "*.exe" -o -name "*.msi" -o -name "*.tar.gz" | xargs sha256sum` - echo "SHA256_SUMS<> $GITHUB_ENV - echo "${SHA256_SUMS}" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - continue-on-error: true - - name: Create release draft - uses: softprops/action-gh-release@v1 - with: - draft: true - fail_on_unmatched_files: true - discussion_category_name: releases - token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} - files: | - *.AppImage - *.zsync - *.asc - *.deb - *.dmg - *.msi - *.exe - body: |- - :construction: Work in Progress - ## What's New - ## Bugfixes - ## Misc - - --- - - :scroll: A complete list of closed issues is available [here](LINK). - - --- - - :floppy_disk: SHA-256 checksums of release artifacts: - ``` - ${{ env.SHA256_SUMS }} - ``` From b37886c98cffb60a56327684386e8718ef49e25c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:10:28 +0100 Subject: [PATCH 085/109] remove debug options [ci skip] --- .github/workflows/win-exe.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 853dcf789..8f821f54d 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -1,7 +1,6 @@ name: Build Windows Installer on: - push: # TODO remove before merging into develop release: types: [published] workflow_dispatch: From e2f872ba9b7459363c0dd01064fae254423d5004 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:18:05 +0100 Subject: [PATCH 086/109] control whether to run `dput` when manually dispatching debian workflow --- .github/workflows/debian.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 807e833a1..83084121d 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -4,6 +4,12 @@ on: release: types: [published] workflow_dispatch: + inputs: + dput: + description: 'Upload to PPA' + required: true + default: false + type: boolean env: JAVA_VERSION: 17 @@ -99,7 +105,7 @@ jobs: cryptomator_*_amd64.deb cryptomator_*.asc - name: Run dput to beta repo - if: startsWith(github.ref, 'refs/tags/') + if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes - name: Publish Debian package on GitHub Releases if: startsWith(github.ref, 'refs/tags/') From 746d3974a01c31c282007397c6585e52f239a07d Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:54:20 +0100 Subject: [PATCH 087/109] treat workflow input as string see https://github.com/actions/runner/issues/1483 --- .github/workflows/debian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 83084121d..3f713bce8 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -105,7 +105,7 @@ jobs: cryptomator_*_amd64.deb cryptomator_*.asc - name: Run dput to beta repo - if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput + if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true' run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes - name: Publish Debian package on GitHub Releases if: startsWith(github.ref, 'refs/tags/') From d680d7fe9d68a8bfa1793b3e7bc159d0c7aba6ae Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 14:58:53 +0100 Subject: [PATCH 088/109] renamed step [ci skip] --- .github/workflows/debian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 3f713bce8..6caee6069 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -104,7 +104,7 @@ jobs: cryptomator_*_source.changes cryptomator_*_amd64.deb cryptomator_*.asc - - name: Run dput to beta repo + - name: Publish on PPA if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.dput == 'true' run: dput ppa:sebastian-stenzel/cryptomator-beta cryptomator_*_source.changes - name: Publish Debian package on GitHub Releases From c22ede3bf4b557efc8d3ed5a267337f921c7ae8c Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 15:52:56 +0100 Subject: [PATCH 089/109] only enforce version if tag contains semver string --- .github/workflows/appimage.yml | 2 +- .github/workflows/debian.yml | 2 +- .github/workflows/mac-dmg.yml | 2 +- .github/workflows/win-exe.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index ef06ef1e2..694f6ee45 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -25,7 +25,7 @@ jobs: - id: versions name: Apply version information run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then SEM_VER_STR=${GITHUB_REF##*/} mvn versions:set -DnewVersion=${SEM_VER_STR} else diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 6caee6069..f7893bbaa 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -35,7 +35,7 @@ jobs: - id: versions name: Apply version information run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then SEM_VER_STR=${GITHUB_REF##*/} mvn versions:set -DnewVersion=${SEM_VER_STR} else diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 0c4043208..58b7e9408 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -25,7 +25,7 @@ jobs: - id: versions name: Apply version information run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then SEM_VER_STR=${GITHUB_REF##*/} mvn versions:set -DnewVersion=${SEM_VER_STR} else diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 8f821f54d..9a855b646 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -30,7 +30,7 @@ jobs: - id: versions name: Apply version information run: | - if [[ $GITHUB_REF == refs/tags/* ]]; then + if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then SEM_VER_STR=${GITHUB_REF##*/} mvn versions:set -DnewVersion=${SEM_VER_STR} else From e67772c9a6848e9d597c212d63beade803972cbf Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 16:10:40 +0100 Subject: [PATCH 090/109] attempt to trigger build when drafting a release --- .github/workflows/appimage.yml | 2 +- .github/workflows/debian.yml | 2 +- .github/workflows/mac-dmg.yml | 2 +- .github/workflows/win-exe.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 694f6ee45..fa4928b75 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -2,7 +2,7 @@ name: Build AppImage on: release: - types: [published] + types: [created] workflow_dispatch: env: diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index f7893bbaa..b6f174e25 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -2,7 +2,7 @@ name: Build Debian Package on: release: - types: [published] + types: [created] workflow_dispatch: inputs: dput: diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 58b7e9408..75015a2fc 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -2,7 +2,7 @@ name: Build macOS .dmg on: release: - types: [published] + types: [created] workflow_dispatch: env: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 9a855b646..387eb0c72 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -2,7 +2,7 @@ name: Build Windows Installer on: release: - types: [published] + types: [created] workflow_dispatch: env: From 0d783183733c1c4771193e2b91997fe39f3a4219 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 16:44:17 +0100 Subject: [PATCH 091/109] Revert "attempt to trigger build when drafting a release" This reverts commit e67772c9a6848e9d597c212d63beade803972cbf. --- .github/workflows/appimage.yml | 2 +- .github/workflows/debian.yml | 2 +- .github/workflows/mac-dmg.yml | 2 +- .github/workflows/win-exe.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index fa4928b75..694f6ee45 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -2,7 +2,7 @@ name: Build AppImage on: release: - types: [created] + types: [published] workflow_dispatch: env: diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index b6f174e25..f7893bbaa 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -2,7 +2,7 @@ name: Build Debian Package on: release: - types: [created] + types: [published] workflow_dispatch: inputs: dput: diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 75015a2fc..58b7e9408 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -2,7 +2,7 @@ name: Build macOS .dmg on: release: - types: [created] + types: [published] workflow_dispatch: env: diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 387eb0c72..9a855b646 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -2,7 +2,7 @@ name: Build Windows Installer on: release: - types: [created] + types: [published] workflow_dispatch: env: From 7b4a3e13138d2239965a8c139d36aef329860835 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 18:06:08 +0100 Subject: [PATCH 092/109] make sure not to upload unrelated artifacts to a release --- .github/workflows/appimage.yml | 6 +++--- .github/workflows/mac-dmg.yml | 4 ++-- .github/workflows/win-exe.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 694f6ee45..aab954476 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -146,6 +146,6 @@ jobs: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | - *.AppImage - *.zsync - *.asc \ No newline at end of file + cryptomator-*.AppImage + cryptomator-*.zsync + cryptomator-*.asc \ No newline at end of file diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 58b7e9408..66af92d6d 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -226,7 +226,7 @@ jobs: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | - *.dmg - *.asc + Cryptomator-*.dmg + Cryptomator-*.asc diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 9a855b646..f024d7347 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -251,5 +251,5 @@ jobs: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | - *.exe - *.asc \ No newline at end of file + Cryptomator-*.exe + Cryptomator-*.asc \ No newline at end of file From 77e2be22dea911e89f8c43033d8e99963067f946 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 18 Mar 2022 18:06:31 +0100 Subject: [PATCH 093/109] updated .gitignore [ci skip] --- dist/mac/dmg/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/mac/dmg/.gitignore b/dist/mac/dmg/.gitignore index c186170c9..b8ef35283 100644 --- a/dist/mac/dmg/.gitignore +++ b/dist/mac/dmg/.gitignore @@ -1,4 +1,5 @@ # created during build +Cryptomator.app/ runtime/ dmg/ -*.dmg +*.dmg \ No newline at end of file From 4a3d579b76b5e8f7906d447d40a03473e442b0aa Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 21 Mar 2022 14:01:35 +0100 Subject: [PATCH 094/109] changed button title as suggested in review --- src/main/resources/fxml/unlock_select_masterkeyfile.fxml | 2 +- src/main/resources/i18n/strings.properties | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml index 43f2f3e46..d37289fca 100644 --- a/src/main/resources/fxml/unlock_select_masterkeyfile.fxml +++ b/src/main/resources/fxml/unlock_select_masterkeyfile.fxml @@ -34,7 +34,7 @@