-
-
Notifications
You must be signed in to change notification settings - Fork 192
Add folder upload support #624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tobihagemann
wants to merge
26
commits into
develop
Choose a base branch
from
feature/upload-folder
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
28ff7cf
Add folder upload support
tobihagemann dac8dff
Add upload service with foreground execution and resume support
tobihagemann 9abeabc
Fix race conditions, error handling, and plurals in upload service
tobihagemann 9f36ab9
Clean up upload service: simplify DAO, remove dead code, fix dialog s…
tobihagemann 62a5063
Fix multi-resume checkpoint loss, simplify dialog and presenter code
tobihagemann 414d878
Fix resume navigation and broaden folder resume exception handling
tobihagemann 9c1ca9f
Add tests for UploadFolderStructure and StreamHelper
tobihagemann 890a78d
Suspend vault auto-lock during upload picker and service flows
tobihagemann 44a6a76
Align upload feature with app architecture patterns
tobihagemann fff3b4e
Migrate changed-file upload to foreground service with temp file cleanup
tobihagemann e1c1ad4
Migrate text editor save to foreground upload service
tobihagemann ebcef06
Queue concurrent uploads and handle dispatch failures
tobihagemann 5267dcf
Fix suspended lock leak, temp file leak, and asymmetric test coverage
tobihagemann 4e6a3c4
Fix lock suspension leaks in presenter destroyed() and upload resume …
tobihagemann 82f5e04
Replace boolean lock suspension with time-bounded keep-alive leases
tobihagemann d871bf8
Fix picker lease leaks and clean up stale checkpoints for abandoned u…
tobihagemann 552b901
Add targeted UI updates via UploadUiUpdates event bus during upload
tobihagemann 27eaf97
Show replace dialog for folder uploads and persist choice for resume
tobihagemann 107cf4c
Fix cancel race, stale checkpoint, and service linger in UploadService
tobihagemann dbf0f20
Show upload status indicator in vault list with animated fill-up icon
tobihagemann 2f0e558
Prevent manual vault lock while upload is in progress
tobihagemann 876ee59
Replace vault lock toast with Cancel & Lock confirmation dialog
tobihagemann 8233d64
Add per-vault upload cancellation so locking one vault does not abort…
tobihagemann 49b50f5
Fix stale checkpoint on pre-cancel and persist URI permissions for fi…
tobihagemann 320e957
Fix file resume to respect checkpoint replacing flag instead of hardc…
tobihagemann bf20294
Include exception in Timber debug log for URI permission failure
tobihagemann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
domain/src/main/java/org/cryptomator/domain/usecases/cloud/StreamHelper.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.cryptomator.domain.usecases.cloud; | ||
|
|
||
| import java.io.Closeable; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.OutputStream; | ||
|
|
||
| class StreamHelper { | ||
|
|
||
| private static final int EOF = -1; | ||
| private static final int BUFFER_SIZE = 4096; | ||
|
|
||
| private StreamHelper() { | ||
| } | ||
|
|
||
| static void copy(InputStream in, OutputStream out) throws IOException { | ||
| byte[] buffer = new byte[BUFFER_SIZE]; | ||
| try { | ||
| int read; | ||
| while ((read = in.read(buffer)) != EOF) { | ||
| out.write(buffer, 0, read); | ||
| } | ||
| } finally { | ||
| closeQuietly(in); | ||
| closeQuietly(out); | ||
| } | ||
| } | ||
|
|
||
| static void closeQuietly(Closeable closeable) { | ||
| if (closeable != null) { | ||
| try { | ||
| closeable.close(); | ||
| } catch (IOException e) { | ||
| // ignore | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFolderFiles.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| package org.cryptomator.domain.usecases.cloud; | ||
|
|
||
| import android.content.Context; | ||
|
|
||
| import org.cryptomator.domain.CloudFile; | ||
| import org.cryptomator.domain.CloudFolder; | ||
| import org.cryptomator.domain.exception.BackendException; | ||
| import org.cryptomator.domain.exception.CancellationException; | ||
| import org.cryptomator.domain.exception.FatalBackendException; | ||
| import org.cryptomator.domain.repository.CloudContentRepository; | ||
| import org.cryptomator.domain.usecases.ProgressAware; | ||
| import org.cryptomator.generator.Parameter; | ||
| import org.cryptomator.generator.UseCase; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileOutputStream; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.OutputStream; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| import static java.io.File.createTempFile; | ||
|
|
||
| @UseCase | ||
| class UploadFolderFiles { | ||
|
|
||
| private final Context context; | ||
| private final CloudContentRepository cloudContentRepository; | ||
| private final CloudFolder parent; | ||
| private final UploadFolderStructure folderStructure; | ||
|
|
||
| private volatile boolean cancelled; | ||
| private final Flag cancelledFlag = new Flag() { | ||
| @Override | ||
| public boolean get() { | ||
| return cancelled; | ||
| } | ||
| }; | ||
|
|
||
| public UploadFolderFiles(Context context, // | ||
| CloudContentRepository cloudContentRepository, // | ||
| @Parameter CloudFolder parent, // | ||
| @Parameter UploadFolderStructure folderStructure) { | ||
| this.context = context; | ||
| this.cloudContentRepository = cloudContentRepository; | ||
| this.parent = parent; | ||
| this.folderStructure = folderStructure; | ||
| } | ||
|
|
||
| public void onCancel() { | ||
| cancelled = true; | ||
| } | ||
|
|
||
| public List<CloudFile> execute(ProgressAware<UploadState> progressAware) throws BackendException { | ||
| cancelled = false; | ||
| try { | ||
| return uploadFolder(parent, folderStructure, progressAware); | ||
| } catch (BackendException | RuntimeException e) { | ||
| if (cancelled) { | ||
| throw new CancellationException(e); | ||
| } else { | ||
| throw e; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private List<CloudFile> uploadFolder(CloudFolder targetParent, UploadFolderStructure structure, ProgressAware<UploadState> progressAware) throws BackendException { | ||
| CloudFolder createdFolder = cloudContentRepository.create( // | ||
| cloudContentRepository.folder(targetParent, structure.getFolderName())); | ||
|
|
||
| List<CloudFile> uploadedFiles = new ArrayList<>(); | ||
|
|
||
| for (UploadFile file : structure.getFiles()) { | ||
| uploadedFiles.add(upload(createdFolder, file, progressAware)); | ||
| } | ||
|
|
||
| for (UploadFolderStructure subfolder : structure.getSubfolders()) { | ||
| uploadedFiles.addAll(uploadFolder(createdFolder, subfolder, progressAware)); | ||
| } | ||
|
|
||
| return uploadedFiles; | ||
| } | ||
|
|
||
| private CloudFile upload(CloudFolder folder, UploadFile uploadFile, ProgressAware<UploadState> progressAware) throws BackendException { | ||
| DataSource dataSource = uploadFile.getDataSource(); | ||
| if (dataSource.size(context) != null) { | ||
| return upload(folder, uploadFile, dataSource, progressAware); | ||
| } else { | ||
| File file = copyDataToFile(dataSource); | ||
| try { | ||
| return upload(folder, uploadFile, FileBasedDataSource.from(file), progressAware); | ||
| } finally { | ||
| file.delete(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private CloudFile upload(CloudFolder folder, UploadFile uploadFile, DataSource dataSource, ProgressAware<UploadState> progressAware) throws BackendException { | ||
| return writeCloudFile( // | ||
| folder, // | ||
| uploadFile.getFileName(), // | ||
| CancelAwareDataSource.wrap(dataSource, cancelledFlag), // | ||
| uploadFile.getReplacing(), // | ||
| progressAware); | ||
| } | ||
|
|
||
| private CloudFile writeCloudFile(CloudFolder folder, String fileName, CancelAwareDataSource dataSource, boolean replacing, ProgressAware<UploadState> progressAware) throws BackendException { | ||
| Long size = dataSource.size(context); | ||
| CloudFile source = cloudContentRepository.file(folder, fileName, size); | ||
| return cloudContentRepository.write( // | ||
| source, // | ||
| dataSource, // | ||
| progressAware, // | ||
| replacing, // | ||
| size); | ||
| } | ||
|
|
||
| private File copyDataToFile(DataSource dataSource) { | ||
| File dir = context.getCacheDir(); | ||
| try { | ||
| File target = createTempFile("upload", "tmp", dir); | ||
| InputStream in = CancelAwareDataSource.wrap(dataSource, cancelledFlag).open(context); | ||
| OutputStream out = new FileOutputStream(target); | ||
| StreamHelper.copy(in, out); | ||
| dataSource.modifiedDate(context).ifPresent(value -> target.setLastModified(value.getTime())); | ||
| return target; | ||
| } catch (IOException e) { | ||
| throw new FatalBackendException(e); | ||
| } | ||
| } | ||
|
|
||
| } | ||
45 changes: 45 additions & 0 deletions
45
domain/src/main/java/org/cryptomator/domain/usecases/cloud/UploadFolderStructure.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package org.cryptomator.domain.usecases.cloud; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class UploadFolderStructure { | ||
|
|
||
| private final String folderName; | ||
| private final List<UploadFile> files; | ||
| private final List<UploadFolderStructure> subfolders; | ||
|
|
||
| public UploadFolderStructure(String folderName) { | ||
| this.folderName = folderName; | ||
| this.files = new ArrayList<>(); | ||
| this.subfolders = new ArrayList<>(); | ||
| } | ||
|
|
||
| public String getFolderName() { | ||
| return folderName; | ||
| } | ||
|
|
||
| public List<UploadFile> getFiles() { | ||
| return files; | ||
| } | ||
|
|
||
| public List<UploadFolderStructure> getSubfolders() { | ||
| return subfolders; | ||
| } | ||
|
|
||
| public void addFile(UploadFile file) { | ||
| this.files.add(file); | ||
| } | ||
|
|
||
| public void addSubfolder(UploadFolderStructure subfolder) { | ||
| this.subfolders.add(subfolder); | ||
| } | ||
|
|
||
| public int totalFileCount() { | ||
| int count = files.size(); | ||
| for (UploadFolderStructure subfolder : subfolders) { | ||
| count += subfolder.totalFileCount(); | ||
| } | ||
| return count; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.