diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-06-21 16:04:42 +0300 |
---|---|---|
committer | Niedermann IT-Dienstleistungen <stefan-niedermann@users.noreply.github.com> | 2020-06-21 17:18:46 +0300 |
commit | f7cca3a2699787728b36268c031d90c99074be8f (patch) | |
tree | 7c761d3f1baed68671d27d4fb0ec7438fc0e1a71 | |
parent | 27623eaac7ccaabba7a4653fdb0152be106e8fbe (diff) |
Enhanced exception handling for uploading attachments
8 files changed, 78 insertions, 84 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/UploadAttachmentFailedException.java b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/UploadAttachmentFailedException.java new file mode 100644 index 000000000..05374669d --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/UploadAttachmentFailedException.java @@ -0,0 +1,12 @@ +package it.niedermann.nextcloud.deck.exceptions; + +public class UploadAttachmentFailedException extends Exception { + + public UploadAttachmentFailedException(String message) { + super(message); + } + + public UploadAttachmentFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java index 1020e5cb3..91192f597 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java @@ -1,15 +1,9 @@ package it.niedermann.nextcloud.deck.model; -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import android.webkit.MimeTypeMap; - import androidx.room.Entity; import androidx.room.ForeignKey; import androidx.room.Index; -import java.io.File; import java.io.Serializable; import java.util.Date; @@ -146,23 +140,6 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac this.localPath = localPath; } - public static String getMimetypeForUri(Context context, Uri uri) { - String extension; - - //Check uri format to avoid null - if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { - //If scheme is a content - extension = context.getContentResolver().getType(uri); - } else { - //If scheme is a File - //This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters. - extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString()); - - } - - return extension; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java index d660b6ecb..452e2d98f 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java @@ -94,7 +94,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment> @Override public void updateOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<Attachment> callback, Attachment entity) { Uri uri = Uri.fromFile(new File(entity.getLocalPath())); - String type = Attachment.getMimetypeForUri(dataBaseAdapter.getContext(), uri); + String type = dataBaseAdapter.getContext().getContentResolver().getType(uri); serverAdapter.updateAttachment(board.getId(), stack.getId(), card.getId(), entity.getId(), type, uri, callback); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/SelectCardActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/SelectCardActivity.java index cb9d8e28b..2e31d3e14 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/SelectCardActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/SelectCardActivity.java @@ -1,7 +1,9 @@ package it.niedermann.nextcloud.deck.ui; +import android.content.ContentResolver; import android.content.Intent; import android.graphics.Typeface; +import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.view.Menu; @@ -13,6 +15,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,13 +24,14 @@ import java.util.Objects; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException; import it.niedermann.nextcloud.deck.model.Board; import it.niedermann.nextcloud.deck.model.full.FullCard; import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder; import it.niedermann.nextcloud.deck.ui.card.SelectCardListener; import it.niedermann.nextcloud.deck.util.ExceptionUtil; -import static it.niedermann.nextcloud.deck.util.AttachmentUtil.appendAttachment; +import static it.niedermann.nextcloud.deck.util.AttachmentUtil.copyContentUriToTempFile; import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard; public class SelectCardActivity extends MainActivity implements SelectCardListener { @@ -77,7 +82,30 @@ public class SelectCardActivity extends MainActivity implements SelectCardListen public void onCardSelected(FullCard fullCard) { try { if (isFile) { - appendAttachment(this, syncManager, mStreamsToUpload, fullCard); + for (Parcelable sourceStream : mStreamsToUpload) { + new Thread(() -> { + if (!(sourceStream instanceof Uri)) { + DeckLog.logError(new UploadAttachmentFailedException("stream is not of type " + Uri.class.getSimpleName())); + return; + } + Uri sourceUri = (Uri) sourceStream; + if (!ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) { + DeckLog.logError(new UploadAttachmentFailedException("Unhandled URI scheme: " + sourceUri.getScheme())); + return; + } + + try { + File copiedFile = copyContentUriToTempFile(this, sourceUri, fullCard.getAccountId(), fullCard.getCard().getLocalId()); + String mimeType = getContentResolver().getType(sourceUri); + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + syncManager.addAttachmentToCard(fullCard.getAccountId(), fullCard.getCard().getLocalId(), mimeType, copiedFile); + } catch (IOException e) { + DeckLog.logError(new UploadAttachmentFailedException("Error while uploading attachment", e)); + } + }).start(); + } } else { appendText(fullCard, receivedText); } diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java index b81411ced..2b2db42e7 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java @@ -22,6 +22,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; import java.io.File; import java.io.IOException; @@ -32,6 +33,7 @@ import java.util.Map; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabAttachmentsBinding; +import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException; import it.niedermann.nextcloud.deck.model.Attachment; import it.niedermann.nextcloud.deck.model.enums.DBStatus; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; @@ -39,12 +41,14 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiv import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment; import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar; import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel; +import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; import static it.niedermann.nextcloud.deck.ui.branding.BrandedActivity.applyBrandToFAB; import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_DEFAULT; import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_IMAGE; import static it.niedermann.nextcloud.deck.util.AttachmentUtil.copyContentUriToTempFile; +import static java.net.HttpURLConnection.HTTP_CONFLICT; public class CardAttachmentsFragment extends BrandedFragment implements AttachmentDeletedListener, AttachmentClickedListener { @@ -158,31 +162,27 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_ADD_ATTACHMENT && resultCode == Activity.RESULT_OK) { if (data == null) { - DeckLog.warn("data is null"); + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Intent data is null"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); return; } final Uri sourceUri = data.getData(); if (sourceUri == null) { - DeckLog.warn("data.getParcelableExtra(Intent.EXTRA_STREAM() returned null"); + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("sourceUri is null"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + return; + } + if (!ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) { + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme: " + sourceUri.getScheme()), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); return; } - DeckLog.info("Uri: " + sourceUri.toString()); + DeckLog.verbose("--- found content URL " + sourceUri.getPath()); File fileToUpload; - if (ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) { - DeckLog.verbose("--- found content URL " + sourceUri.getPath()); - try { - DeckLog.verbose("---- so, now copy&upload: " + sourceUri.getPath()); - fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, viewModel.getAccount().getId(), viewModel.getFullCard().getCard().getLocalId()); - } catch (IOException e) { - // TODO error popup - e.printStackTrace(); - return; - } - } else { - // TODO error popup - DeckLog.warn("can not handle file"); + try { + DeckLog.verbose("---- so, now copy & upload: " + sourceUri.getPath()); + fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, viewModel.getAccount().getId(), viewModel.getFullCard().getCard().getLocalId()); + } catch (IOException e) { + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Could not copy content URI to temporary file", e), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); return; } @@ -196,7 +196,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme final Date now = new Date(); final Attachment a = new Attachment(); - a.setMimetype(Attachment.getMimetypeForUri(getContext(), sourceUri)); + a.setMimetype(requireContext().getContentResolver().getType(sourceUri)); a.setData(fileToUpload.getName()); a.setFilename(fileToUpload.getName()); a.setBasename(fileToUpload.getName()); @@ -208,12 +208,17 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme viewModel.getFullCard().getAttachments().add(a); adapter.addAttachment(a); if (!viewModel.isCreateMode()) { - WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId(), Attachment.getMimetypeForUri(getContext(), sourceUri), fileToUpload); + WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId(), a.getMimetype(), fileToUpload); observeOnce(liveData, getViewLifecycleOwner(), (next) -> { if (liveData.hasError()) { - viewModel.getFullCard().getAttachments().remove(a); - adapter.removeAttachment(a); - BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show(); + Throwable t = liveData.getError(); + if (t instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) t).getStatusCode() == HTTP_CONFLICT) { + viewModel.getFullCard().getAttachments().remove(a); + adapter.removeAttachment(a); + BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show(); + } else { + ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", t), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + } } else { viewModel.getFullCard().getAttachments().remove(a); adapter.removeAttachment(a); @@ -224,6 +229,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme } updateEmptyContentView(); } + } @Override diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java index 7d2d0c983..9ab9b70c8 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java @@ -28,6 +28,7 @@ import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.DialogExceptionBinding; import it.niedermann.nextcloud.deck.exceptions.DeckException; +import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.ui.exception.tips.TipsAdapter; import it.niedermann.nextcloud.deck.util.ExceptionUtil; @@ -113,6 +114,8 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment { adapter.add(R.string.error_dialog_insufficient_storage); break; } + } else if (throwable instanceof UploadAttachmentFailedException) { + adapter.add(R.string.error_dialog_attachment_upload_failed); } else if (throwable instanceof DeckException) { switch (((DeckException) throwable).getHint()) { case CAPABILITIES_VERSION_NOT_PARSABLE: diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java index 5addd0c34..6d8280bec 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java @@ -1,9 +1,7 @@ package it.niedermann.nextcloud.deck.util; -import android.content.ContentResolver; import android.content.Context; import android.net.Uri; -import android.os.Parcelable; import androidx.annotation.NonNull; @@ -12,11 +10,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.List; import it.niedermann.nextcloud.deck.DeckLog; -import it.niedermann.nextcloud.deck.model.full.FullCard; -import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; /** * Created by stefan on 07.03.20. @@ -31,34 +26,6 @@ public class AttachmentUtil { return accountUrl + "/index.php/apps/deck/cards/" + cardRemoteId + "/attachment/" + attachmentRemoteId; } - public static void appendAttachment(@NonNull Context context, @NonNull SyncManager syncManager, @NonNull List<Parcelable> streamsToUpload, @NonNull FullCard fullCard) { - for (Parcelable sourceStream : streamsToUpload) { - new Thread(() -> { - Uri sourceUri = (Uri) sourceStream; - if (sourceUri != null) { - if (ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) { - /// content: uris will be copied to temporary files before calling {@link FileUploader} - try { - DeckLog.verbose("---- so, now copy & upload: " + sourceUri.getPath()); - File copiedFile = copyContentUriToTempFile(context, sourceUri, fullCard.getAccountId(), fullCard.getCard().getLocalId()); - String mimeType = context.getContentResolver().getType(sourceUri); - if (mimeType == null) { - mimeType = "application/octet-stream"; - } - syncManager.addAttachmentToCard(fullCard.getAccountId(), fullCard.getCard().getLocalId(), mimeType, copiedFile); - } catch (IOException e) { - e.printStackTrace(); - } - DeckLog.verbose("--- found content URL, remember for later: " + sourceUri.getPath()); - } else { //if (ContentResolver.SCHEME_FILE.equals(sourceUri.getScheme())) { - // TODO can not handle this type - DeckLog.verbose("--- found file URL, directly upload: " + sourceUri.getPath()); - } - } - }).start(); - } - } - public static File copyContentUriToTempFile(@NonNull Context context, @NonNull Uri currentUri, long accountId, Long localId) throws IOException { String fullTempPath = context.getApplicationContext().getFilesDir().getAbsolutePath() + "/attachments/account-" + accountId + "/card-" + (localId == null ? "pending-creation" : localId) + '/' + UriUtils.getDisplayNameForUri(currentUri, context); DeckLog.verbose("----- fullTempPath: " + fullTempPath); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0481bc04..c2441fc93 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,10 +117,10 @@ <string name="url_license" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck/blob/master/LICENSE</string> <string name="url_translations" translatable="false">https://www.transifex.com/nextcloud/nextcloud/</string> <string name="url_about_icons_disclaimer_mdi" translatable="false">https://materialdesignicons.com/</string> - <string name="url_fragment_update_deck" translatable="false">/index.php/settings/apps/installed/deck</string> - <string name="url_fragment_install_deck" translatable="false">/index.php/settings/apps/organization/deck</string> - <string name="url_fragment_server_logs" translatable="false">/index.php/settings/admin/logging</string> - <string name="url_fragment_share_card_pre_1_0_0" translatable="false">/index.php/apps/deck/#!/board/%1$d/card/%2$d</string> + <string name="url_fragment_update_deck" translatable="false">/index.php/settings/apps/installed/deck</string> + <string name="url_fragment_install_deck" translatable="false">/index.php/settings/apps/organization/deck</string> + <string name="url_fragment_server_logs" translatable="false">/index.php/settings/admin/logging</string> + <string name="url_fragment_share_card_pre_1_0_0" translatable="false">/index.php/apps/deck/#!/board/%1$d/card/%2$d</string> <string name="url_fragment_share_card_since_1_0_0" translatable="false">/index.php/apps/deck/#/board/%1$d/card/%2$d</string> <string name="card_edit_details">Details</string> @@ -255,6 +255,7 @@ <string name="error_dialog_redirect">Your server did respond with a HTTP 302 status code, which implies, that you do not have installed the Deck app on your server or something is misconfigured. This can be caused by custom overrides in a .htaccess-file or by Nextcloud apps like OID Client.</string> <string name="error_dialog_version_not_parsable">We could not determine the version of the server side Deck app. Please make sure it is installed and enabled.</string> <string name="error_dialog_capabilities_not_parsable">We could not fetch the capabilities of your server. Please make sure your server is running well and other client apps are able to access Nextcloud.</string> + <string name="error_dialog_attachment_upload_failed">An attachment could not be uploaded. Please try to share it on another way and let us know about this bug.</string> <string name="error_action_open_deck_info">Open App info</string> <string name="error_action_open_network">Network settings</string> <string name="error_action_server_logs">Server logs</string> |