From 940338625023d9a24a589d0a087f3b077f3ea0b2 Mon Sep 17 00:00:00 2001 From: ByteHamster Date: Sun, 27 Feb 2022 11:09:36 +0100 Subject: Separate UI (download error messages) from logic (download errors) (#5755) --- .../activity/OnlineFeedViewActivity.java | 3 +- .../antennapod/adapter/DownloadLogAdapter.java | 3 +- .../download/DownloadServiceNotification.java | 3 +- .../core/service/download/DownloadStatus.java | 167 +++++++++------------ .../core/service/download/Downloader.java | 4 +- .../danoeh/antennapod/core/storage/DBReader.java | 5 +- .../storage/mapper/DownloadStatusCursorMapper.java | 35 +++++ .../danoeh/antennapod/core/util/DownloadError.java | 59 +++----- .../antennapod/core/util/DownloadErrorLabel.java | 45 ++++++ 9 files changed, 191 insertions(+), 133 deletions(-) create mode 100644 core/src/main/java/de/danoeh/antennapod/core/storage/mapper/DownloadStatusCursorMapper.java create mode 100644 core/src/main/java/de/danoeh/antennapod/core/util/DownloadErrorLabel.java diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 277cdf6aa..ac22baf43 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -35,6 +35,7 @@ import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.DownloadRequestCreator; import de.danoeh.antennapod.core.feed.FeedUrlNotFoundException; +import de.danoeh.antennapod.core.util.DownloadErrorLabel; import de.danoeh.antennapod.discovery.CombinedSearcher; import de.danoeh.antennapod.discovery.PodcastSearchResult; import de.danoeh.antennapod.event.FeedListUpdateEvent; @@ -327,7 +328,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { dialog.show(); } } else { - showErrorDialog(status.getReason().getErrorString(OnlineFeedViewActivity.this), status.getReasonDetailed()); + showErrorDialog(getString(DownloadErrorLabel.from(status.getReason())), status.getReasonDetailed()); } } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index 072ca8acf..d6a74b545 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -21,6 +21,7 @@ import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.util.DownloadError; +import de.danoeh.antennapod.core.util.DownloadErrorLabel; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.ui.common.ThemeUtils; @@ -109,7 +110,7 @@ public class DownloadLogAdapter extends BaseAdapter { holder.icon.setText("{fa-times-circle}"); } holder.icon.setContentDescription(context.getString(R.string.error_label)); - holder.reason.setText(status.getReason().getErrorString(context)); + holder.reason.setText(DownloadErrorLabel.from(status.getReason())); holder.reason.setVisibility(View.VISIBLE); holder.tapForDetails.setVisibility(View.VISIBLE); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java index b80a20a25..48a9e8017 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java @@ -9,6 +9,7 @@ import android.util.Log; import androidx.core.app.NotificationCompat; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.util.DownloadErrorLabel; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.util.gui.NotificationUtils; @@ -117,7 +118,7 @@ public class DownloadServiceNotification { continue; } sb.append("• ").append(statuses.get(i).getTitle()); - sb.append(": ").append(statuses.get(i).getReason().getErrorString(context)); + sb.append(": ").append(DownloadErrorLabel.from(statuses.get(i).getReason())); if (i != statuses.size() - 1) { sb.append("\n"); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java index 8c95dab85..9d4f1ab43 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadStatus.java @@ -1,112 +1,97 @@ package de.danoeh.antennapod.core.service.download; -import android.database.Cursor; import androidx.annotation.NonNull; import java.util.Date; import de.danoeh.antennapod.model.feed.FeedFile; -import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.DownloadError; -/** Contains status attributes for one download */ +/** + * Contains status attributes for one download + */ public class DownloadStatus { - /** - * Downloaders should use this constant for the size attribute if necessary - * so that the listadapters etc. can react properly. - */ - public static final int SIZE_UNKNOWN = -1; - - // ----------------------------------- ATTRIBUTES STORED IN DB - /** Unique id for storing the object in database. */ - private long id; - /** - * A human-readable string which is shown to the user so that he can - * identify the download. Should be the title of the item/feed/media or the - * URL if the download has no other title. - */ + /** + * Downloaders should use this constant for the size attribute if necessary + * so that the listadapters etc. can react properly. + */ + public static final int SIZE_UNKNOWN = -1; + + // ----------------------------------- ATTRIBUTES STORED IN DB + /** + * A human-readable string which is shown to the user so that he can + * identify the download. Should be the title of the item/feed/media or the + * URL if the download has no other title. + */ private final String title; - private DownloadError reason; - /** - * A message which can be presented to the user to give more information. - * Should be null if Download was successful. - */ - private String reasonDetailed; - private boolean successful; - private Date completionDate; - private final long feedfileId; - /** - * Is used to determine the type of the feedfile even if the feedfile does - * not exist anymore. The value should be FEEDFILETYPE_FEED, - * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA - */ + private final long feedfileId; + /** + * Is used to determine the type of the feedfile even if the feedfile does + * not exist anymore. The value should be FEEDFILETYPE_FEED, + * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA + */ private final int feedfileType; private final boolean initiatedByUser; - - // ------------------------------------ NOT STORED IN DB + /** + * Unique id for storing the object in database. + */ + private long id; + private DownloadError reason; + /** + * A message which can be presented to the user to give more information. + * Should be null if Download was successful. + */ + private String reasonDetailed; + private boolean successful; + private final Date completionDate; + // ------------------------------------ NOT STORED IN DB private boolean done; - private boolean cancelled; + private boolean cancelled; - public DownloadStatus(@NonNull DownloadRequest request, DownloadError reason, boolean successful, boolean cancelled, - String reasonDetailed) { - this(0, request.getTitle(), request.getFeedfileId(), request.getFeedfileType(), successful, cancelled, false, - reason, new Date(), reasonDetailed, request.isInitiatedByUser()); - } + /** + * Constructor for creating new completed downloads. + */ + public DownloadStatus(@NonNull FeedFile feedfile, String title, DownloadError reason, boolean successful, + String reasonDetailed, boolean initiatedByUser) { + this(0, title, feedfile.getId(), feedfile.getTypeAsInt(), successful, false, true, reason, new Date(), + reasonDetailed, initiatedByUser); + } - /** Constructor for creating new completed downloads. */ - public DownloadStatus(@NonNull FeedFile feedfile, String title, DownloadError reason, boolean successful, + public DownloadStatus(long id, String title, long feedfileId, int feedfileType, boolean successful, + boolean cancelled, boolean done, DownloadError reason, Date completionDate, String reasonDetailed, boolean initiatedByUser) { - this(0, title, feedfile.getId(), feedfile.getTypeAsInt(), successful, false, true, reason, new Date(), - reasonDetailed, initiatedByUser); - } - - public static DownloadStatus fromCursor(Cursor cursor) { - int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); - int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE); - int indexFeedFile = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILE); - int indexFileFileType = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILETYPE); - int indexSuccessful = cursor.getColumnIndex(PodDBAdapter.KEY_SUCCESSFUL); - int indexReason = cursor.getColumnIndex(PodDBAdapter.KEY_REASON); - int indexCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_COMPLETION_DATE); - int indexReasonDetailed = cursor.getColumnIndex(PodDBAdapter.KEY_REASON_DETAILED); - - return new DownloadStatus(cursor.getLong(indexId), cursor.getString(indexTitle), cursor.getLong(indexFeedFile), - cursor.getInt(indexFileFileType), cursor.getInt(indexSuccessful) > 0, false, true, - DownloadError.fromCode(cursor.getInt(indexReason)), - new Date(cursor.getLong(indexCompletionDate)), - cursor.getString(indexReasonDetailed), false); - } - - private DownloadStatus(long id, String title, long feedfileId, int feedfileType, boolean successful, - boolean cancelled, boolean done, DownloadError reason, Date completionDate, - String reasonDetailed, boolean initiatedByUser) { - this.id = id; - this.title = title; - this.feedfileId = feedfileId; - this.reason = reason; - this.successful = successful; - this.cancelled = cancelled; - this.done = done; - this.completionDate = (Date) completionDate.clone(); - this.reasonDetailed = reasonDetailed; - this.feedfileType = feedfileType; - this.initiatedByUser = initiatedByUser; - } - - @Override - public String toString() { - return "DownloadStatus [id=" + id + ", title=" + title + ", reason=" - + reason + ", reasonDetailed=" + reasonDetailed - + ", successful=" + successful + ", completionDate=" - + completionDate + ", feedfileId=" + feedfileId - + ", feedfileType=" + feedfileType + ", done=" + done - + ", cancelled=" + cancelled + "]"; - } + this.id = id; + this.title = title; + this.feedfileId = feedfileId; + this.reason = reason; + this.successful = successful; + this.cancelled = cancelled; + this.done = done; + this.completionDate = (Date) completionDate.clone(); + this.reasonDetailed = reasonDetailed; + this.feedfileType = feedfileType; + this.initiatedByUser = initiatedByUser; + } + + @Override + @NonNull + public String toString() { + return "DownloadStatus [id=" + id + ", title=" + title + ", reason=" + + reason + ", reasonDetailed=" + reasonDetailed + + ", successful=" + successful + ", completionDate=" + + completionDate + ", feedfileId=" + feedfileId + + ", feedfileType=" + feedfileType + ", done=" + done + + ", cancelled=" + cancelled + "]"; + } public long getId() { return id; } + public void setId(long id) { + this.id = id; + } + public String getTitle() { return title; } @@ -135,7 +120,9 @@ public class DownloadStatus { return feedfileType; } - public boolean isInitiatedByUser() { return initiatedByUser; } + public boolean isInitiatedByUser() { + return initiatedByUser; + } public boolean isDone() { return done; @@ -164,8 +151,4 @@ public class DownloadStatus { this.done = true; this.cancelled = true; } - - public void setId(long id) { - this.id = id; - } } \ No newline at end of file diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java index 862852710..3cc16d12a 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/Downloader.java @@ -4,6 +4,7 @@ import android.content.Context; import android.net.wifi.WifiManager; import androidx.annotation.NonNull; +import java.util.Date; import java.util.concurrent.Callable; import de.danoeh.antennapod.core.ClientConfig; @@ -29,7 +30,8 @@ public abstract class Downloader implements Callable { this.request = request; this.request.setStatusMsg(R.string.download_pending); this.cancelled = false; - this.result = new DownloadStatus(request, null, false, false, null); + this.result = new DownloadStatus(0, request.getTitle(), request.getFeedfileId(), request.getFeedfileType(), + false, cancelled, false, null, new Date(), null, request.isInitiatedByUser()); } protected abstract void download(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index 11ff813a9..c7b72c332 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import de.danoeh.antennapod.core.storage.mapper.DownloadStatusCursorMapper; import de.danoeh.antennapod.model.feed.Chapter; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; @@ -451,7 +452,7 @@ public final class DBReader { try (Cursor cursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE)) { List downloadLog = new ArrayList<>(cursor.getCount()); while (cursor.moveToNext()) { - downloadLog.add(DownloadStatus.fromCursor(cursor)); + downloadLog.add(DownloadStatusCursorMapper.convert(cursor)); } Collections.sort(downloadLog, new DownloadStatusComparator()); return downloadLog; @@ -475,7 +476,7 @@ public final class DBReader { try (Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feedId)) { List downloadLog = new ArrayList<>(cursor.getCount()); while (cursor.moveToNext()) { - downloadLog.add(DownloadStatus.fromCursor(cursor)); + downloadLog.add(DownloadStatusCursorMapper.convert(cursor)); } Collections.sort(downloadLog, new DownloadStatusComparator()); return downloadLog; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/DownloadStatusCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/DownloadStatusCursorMapper.java new file mode 100644 index 000000000..f8b57b819 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/DownloadStatusCursorMapper.java @@ -0,0 +1,35 @@ +package de.danoeh.antennapod.core.storage.mapper; + +import android.database.Cursor; +import androidx.annotation.NonNull; +import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.core.util.DownloadError; + +import java.util.Date; + +/** + * Converts a {@link Cursor} to a {@link DownloadStatus} object. + */ +public abstract class DownloadStatusCursorMapper { + /** + * Create a {@link DownloadStatus} instance from a database row (cursor). + */ + @NonNull + public static DownloadStatus convert(@NonNull Cursor cursor) { + int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); + int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE); + int indexFeedFile = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILE); + int indexFileFileType = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDFILETYPE); + int indexSuccessful = cursor.getColumnIndex(PodDBAdapter.KEY_SUCCESSFUL); + int indexReason = cursor.getColumnIndex(PodDBAdapter.KEY_REASON); + int indexCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_COMPLETION_DATE); + int indexReasonDetailed = cursor.getColumnIndex(PodDBAdapter.KEY_REASON_DETAILED); + + return new DownloadStatus(cursor.getLong(indexId), cursor.getString(indexTitle), cursor.getLong(indexFeedFile), + cursor.getInt(indexFileFileType), cursor.getInt(indexSuccessful) > 0, false, true, + DownloadError.fromCode(cursor.getInt(indexReason)), + new Date(cursor.getLong(indexCompletionDate)), + cursor.getString(indexReasonDetailed), false); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java index 9e5282576..06ec8396f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadError.java @@ -1,41 +1,35 @@ package de.danoeh.antennapod.core.util; -import android.content.Context; - -import de.danoeh.antennapod.core.R; - /** Utility class for Download Errors. */ public enum DownloadError { - SUCCESS(0, R.string.download_successful), - ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception), - ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type), - ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error), - ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown), - ERROR_IO_ERROR(5, R.string.download_error_io_error), - ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown), - ERROR_DOWNLOAD_CANCELLED(7, R.string.download_canceled_msg), - ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found), - ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error), - ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space), - ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host), - ERROR_REQUEST_ERROR(12, R.string.download_error_request_error), - ERROR_DB_ACCESS_ERROR(13, R.string.download_error_db_access), - ERROR_UNAUTHORIZED(14, R.string.download_error_unauthorized), - ERROR_FILE_TYPE(15, R.string.download_error_file_type_type), - ERROR_FORBIDDEN(16, R.string.download_error_forbidden), - ERROR_IO_WRONG_SIZE(17, R.string.download_error_wrong_size), - ERROR_IO_BLOCKED(18, R.string.download_error_blocked), - ERROR_UNSUPPORTED_TYPE_HTML(19, R.string.download_error_unsupported_type_html), - ERROR_NOT_FOUND(20, R.string.download_error_not_found), - ERROR_CERTIFICATE(21, R.string.download_error_certificate), - ERROR_PARSER_EXCEPTION_DUPLICATE(22, R.string.download_error_parser_exception); + SUCCESS(0), + ERROR_PARSER_EXCEPTION(1), + ERROR_UNSUPPORTED_TYPE(2), + ERROR_CONNECTION_ERROR(3), + ERROR_MALFORMED_URL(4), + ERROR_IO_ERROR(5), + ERROR_FILE_EXISTS(6), + ERROR_DOWNLOAD_CANCELLED(7), + ERROR_DEVICE_NOT_FOUND(8), + ERROR_HTTP_DATA_ERROR(9), + ERROR_NOT_ENOUGH_SPACE(10), + ERROR_UNKNOWN_HOST(11), + ERROR_REQUEST_ERROR(12), + ERROR_DB_ACCESS_ERROR(13), + ERROR_UNAUTHORIZED(14), + ERROR_FILE_TYPE(15), + ERROR_FORBIDDEN(16), + ERROR_IO_WRONG_SIZE(17), + ERROR_IO_BLOCKED(18), + ERROR_UNSUPPORTED_TYPE_HTML(19), + ERROR_NOT_FOUND(20), + ERROR_CERTIFICATE(21), + ERROR_PARSER_EXCEPTION_DUPLICATE(22); private final int code; - private final int resId; - DownloadError(int code, int resId) { + DownloadError(int code) { this.code = code; - this.resId = resId; } /** Return DownloadError from its associated code. */ @@ -52,9 +46,4 @@ public enum DownloadError { public int getCode() { return code; } - - /** Get a human-readable string. */ - public String getErrorString(Context context) { - return context.getString(resId); - } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DownloadErrorLabel.java b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadErrorLabel.java new file mode 100644 index 000000000..8834350d1 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DownloadErrorLabel.java @@ -0,0 +1,45 @@ +package de.danoeh.antennapod.core.util; + +import androidx.annotation.StringRes; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.R; + +/** + * Provides user-visible labels for download errors. + */ +public class DownloadErrorLabel { + + @StringRes + public static int from(DownloadError error) { + switch (error) { + case SUCCESS: return R.string.download_successful; + case ERROR_PARSER_EXCEPTION: return R.string.download_error_parser_exception; + case ERROR_UNSUPPORTED_TYPE: return R.string.download_error_unsupported_type; + case ERROR_CONNECTION_ERROR: return R.string.download_error_connection_error; + case ERROR_MALFORMED_URL: return R.string.download_error_error_unknown; + case ERROR_IO_ERROR: return R.string.download_error_io_error; + case ERROR_FILE_EXISTS: return R.string.download_error_error_unknown; + case ERROR_DOWNLOAD_CANCELLED: return R.string.download_canceled_msg; + case ERROR_DEVICE_NOT_FOUND: return R.string.download_error_device_not_found; + case ERROR_HTTP_DATA_ERROR: return R.string.download_error_http_data_error; + case ERROR_NOT_ENOUGH_SPACE: return R.string.download_error_insufficient_space; + case ERROR_UNKNOWN_HOST: return R.string.download_error_unknown_host; + case ERROR_REQUEST_ERROR: return R.string.download_error_request_error; + case ERROR_DB_ACCESS_ERROR: return R.string.download_error_db_access; + case ERROR_UNAUTHORIZED: return R.string.download_error_unauthorized; + case ERROR_FILE_TYPE: return R.string.download_error_file_type_type; + case ERROR_FORBIDDEN: return R.string.download_error_forbidden; + case ERROR_IO_WRONG_SIZE: return R.string.download_error_wrong_size; + case ERROR_IO_BLOCKED: return R.string.download_error_blocked; + case ERROR_UNSUPPORTED_TYPE_HTML: return R.string.download_error_unsupported_type_html; + case ERROR_NOT_FOUND: return R.string.download_error_not_found; + case ERROR_CERTIFICATE: return R.string.download_error_certificate; + case ERROR_PARSER_EXCEPTION_DUPLICATE: return R.string.download_error_parser_exception; + default: + if (BuildConfig.DEBUG) { + throw new IllegalArgumentException("No mapping from download error to label"); + } + return R.string.download_error_error_unknown; + } + } +} -- cgit v1.2.3