+ * If exceeding the limit during a file operation, an exception is thrown.
+ *
+ * @since 1.9.8
+ */
+ MAX_PATH_LENGTH,
};
private final Set
+ * This method is invoked on the thread that runs the migration.
+ * If you want to perform longer-running actions such as waiting for user feedback on the UI thread,
+ * consider subclassing {@link SimpleMigrationContinuationListener}.
+ *
+ * @param event The migration event that occurred
+ * @see SimpleMigrationContinuationListener
+ * @return How to proceed with the migration
+ */
+ ContinuationResult continueMigrationOnEvent(ContinuationEvent event);
+
+ enum ContinuationResult {
+ CANCEL, PROCEED
+ }
+
+ enum ContinuationEvent {
+ /**
+ * Migrator wants to do a full recursive directory listing. This might take a while.
+ */
+ REQUIRES_FULL_VAULT_DIR_SCAN
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
index 3d6790f6..6973e8ac 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/api/Migrator.java
@@ -41,6 +41,22 @@ default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence pass
* @throws UnsupportedVaultFormatException
* @throws IOException
*/
- void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException;
+ default void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
+ migrate(vaultRoot, masterkeyFilename, passphrase, progressListener, (event) -> MigrationContinuationListener.ContinuationResult.CANCEL);
+ }
+
+ /**
+ * Performs the migration this migrator is built for.
+ *
+ * @param vaultRoot
+ * @param masterkeyFilename
+ * @param passphrase
+ * @param progressListener
+ * @param continuationListener
+ * @throws InvalidPassphraseException
+ * @throws UnsupportedVaultFormatException
+ * @throws IOException
+ */
+ void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException;
}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListener.java b/src/main/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListener.java
new file mode 100644
index 00000000..03befdc9
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/api/SimpleMigrationContinuationListener.java
@@ -0,0 +1,54 @@
+package org.cryptomator.cryptofs.migration.api;
+
+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 abstract class SimpleMigrationContinuationListener implements MigrationContinuationListener {
+
+ private final Lock lock = new ReentrantLock();
+ private final Condition waitForResult = lock.newCondition();
+ private final AtomicReference
+ * Usually you want to ask for user feedback on the UI thread at this point.
+ *
+ * @param event The migration event that occurred
+ * @apiNote This method is called from the migrator thread
+ */
+ public abstract void migrationHaltedDueToEvent(ContinuationEvent event);
+
+ /**
+ * Continues the migration on its original thread with the desired ContinuationResult.
+ *
+ * @param result How to proceed with the migration
+ * @apiNote This method can be called from any thread.
+ */
+ public final void continueMigrationWithResult(ContinuationResult result) {
+ lock.lock();
+ try {
+ atomicResult.set(result);
+ waitForResult.signal();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public final ContinuationResult continueMigrationOnEvent(ContinuationEvent event) {
+ migrationHaltedDueToEvent(event);
+ lock.lock();
+ try {
+ waitForResult.await();
+ return atomicResult.get();
+ } catch (InterruptedException e) {
+ Thread.interrupted();
+ return ContinuationResult.CANCEL;
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
index 1505d6aa..33fc970a 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v6/Version6Migrator.java
@@ -17,6 +17,7 @@
import org.cryptomator.cryptofs.common.MasterkeyBackupFileHasher;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.migration.api.MigrationContinuationListener;
import org.cryptomator.cryptofs.migration.api.MigrationProgressListener;
import org.cryptomator.cryptofs.migration.api.Migrator;
import org.cryptomator.cryptolib.api.Cryptor;
@@ -39,7 +40,7 @@ public Version6Migrator(CryptorProvider cryptorProvider) {
}
@Override
- public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
+ public void migrate(Path vaultRoot, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws InvalidPassphraseException, UnsupportedVaultFormatException, IOException {
LOG.info("Upgrading {} from version 5 to version 6.", vaultRoot);
progressListener.update(MigrationProgressListener.ProgressState.INITIALIZING, 0.0);
Path masterkeyFile = vaultRoot.resolve(masterkeyFilename);
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
index 7f33177a..158efcf3 100644
--- a/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/FilePathMigration.java
@@ -29,7 +29,7 @@ class FilePathMigration {
private static final Pattern OLD_CANONICAL_FILENAME_PATTERN = Pattern.compile("(0|1S)?([A-Z2-7]{8})*[A-Z2-7=]{8}");
private static final BaseEncoding BASE32 = BaseEncoding.base32();
private static final BaseEncoding BASE64 = BaseEncoding.base64Url();
- private static final int SHORTENING_THRESHOLD = 222; // see calculations in https://github.com/cryptomator/cryptofs/issues/60
+ private static final int SHORTENING_THRESHOLD = 220; // see calculations in https://github.com/cryptomator/cryptofs/issues/60
private static final String OLD_DIRECTORY_PREFIX = "0";
private static final String OLD_SYMLINK_PREFIX = "1S";
private static final String NEW_REGULAR_SUFFIX = ".c9r";
@@ -150,7 +150,6 @@ public Path migrate() throws IOException {
* @param attemptSuffix Empty string or anything starting with a non base64 delimiter
* @return The path after successful migration of {@link #oldPath} if migration is successful for the given attemptSuffix
*/
- // visible for testing
Path getTargetPath(String attemptSuffix) throws InvalidOldFilenameException {
final String canonicalInflatedName = getNewInflatedName();
final String canonicalDeflatedName = getNewDeflatedName();
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/v7/VaultStatsVisitor.java b/src/main/java/org/cryptomator/cryptofs/migration/v7/VaultStatsVisitor.java
new file mode 100644
index 00000000..7a8b4fd6
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/v7/VaultStatsVisitor.java
@@ -0,0 +1,75 @@
+package org.cryptomator.cryptofs.migration.v7;
+
+import org.cryptomator.cryptofs.common.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Optional;
+
+public class VaultStatsVisitor extends SimpleFileVisitor
*
@@ -92,12 +93,14 @@ public boolean needsMigration(Path pathToVault, String masterkeyFilename) throws
* @param pathToVault Path to the vault's root
* @param masterkeyFilename Name of the masterkey file located in the vault
* @param passphrase The passphrase needed to unlock the vault
+ * @param progressListener Listener that will get notified of progress updates
+ * @param continuationListener Listener that will get asked if there are events that require feedback
* @throws NoApplicableMigratorException If the vault can not be migrated, because no migrator could be found
* @throws InvalidPassphraseException If the passphrase could not be used to unlock the vault
* @throws FileSystemCapabilityChecker.MissingCapabilityException If the underlying filesystem lacks features required to store a vault
* @throws IOException if an I/O error occurs migrating the vault
*/
- public void migrate(Path pathToVault, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException {
+ public void migrate(Path pathToVault, String masterkeyFilename, CharSequence passphrase, MigrationProgressListener progressListener, MigrationContinuationListener continuationListener) throws NoApplicableMigratorException, InvalidPassphraseException, IOException {
fsCapabilityChecker.assertAllCapabilities(pathToVault);
Path masterKeyPath = pathToVault.resolve(masterkeyFilename);
@@ -106,7 +109,7 @@ public void migrate(Path pathToVault, String masterkeyFilename, CharSequence pas
try {
Migrator migrator = findApplicableMigrator(keyFile.getVersion()).orElseThrow(NoApplicableMigratorException::new);
- migrator.migrate(pathToVault, masterkeyFilename, passphrase, progressListener);
+ migrator.migrate(pathToVault, masterkeyFilename, passphrase, progressListener, continuationListener);
} catch (UnsupportedVaultFormatException e) {
// might be a tampered masterkey file, as this exception is also thrown if the vault version MAC is not authentic.
throw new IllegalStateException("Vault version checked beforehand but not supported by migrator.");
diff --git a/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java
new file mode 100644
index 00000000..a531565f
--- /dev/null
+++ b/src/main/java/org/cryptomator/cryptofs/migration/api/MigrationContinuationListener.java
@@ -0,0 +1,30 @@
+package org.cryptomator.cryptofs.migration.api;
+
+@FunctionalInterface
+public interface MigrationContinuationListener {
+
+ /**
+ * Invoked when the migration requires action.
+ *
* if (Migrators.get().{@link #needsMigration(Path, String) needsMigration(pathToVault, masterkeyFileName)}) {
- * Migrators.get().{@link #migrate(Path, String, CharSequence, MigrationProgressListener) migrate(pathToVault, masterkeyFileName, passphrase, migrationProgressListener)};
+ * Migrators.get().{@link #migrate(Path, String, CharSequence, MigrationProgressListener, MigrationContinuationListener) migrate(pathToVault, masterkeyFileName, passphrase, progressListener, continuationListener)};
* }
*
*