diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java index da946cf0b..dd8a466eb 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.core.feed; -import android.database.Cursor; import android.text.TextUtils; import androidx.annotation.Nullable; @@ -9,11 +8,10 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.core.util.SortOrder; /** - * Data Object for a whole feed + * Data Object for a whole feed. * * @author daniel */ @@ -24,9 +22,14 @@ public class Feed extends FeedFile { public static final String TYPE_ATOM1 = "atom"; public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:"; - /* title as defined by the feed */ + /** + * title as defined by the feed. + */ private String feedTitle; - /* custom title set by the user */ + + /** + * custom title set by the user. + */ private String customTitle; /** @@ -40,25 +43,25 @@ public class Feed extends FeedFile { private String description; private String language; /** - * Name of the author + * Name of the author. */ private String author; private String imageUrl; private List items; /** - * String that identifies the last update (adopted from Last-Modified or ETag header) + * String that identifies the last update (adopted from Last-Modified or ETag header). */ private String lastUpdate; private String paymentLink; /** - * Feed type, for example RSS 2 or Atom + * Feed type, for example RSS 2 or Atom. */ private String type; /** - * Feed preferences + * Feed preferences. */ private FeedPreferences preferences; @@ -120,7 +123,7 @@ public class Feed extends FeedFile { this.paged = paged; this.nextPageLink = nextPageLink; this.items = new ArrayList<>(); - if(filter != null) { + if (filter != null) { this.itemfilter = new FeedItemFilter(filter); } else { this.itemfilter = new FeedItemFilter(new String[0]); @@ -130,7 +133,7 @@ public class Feed extends FeedFile { } /** - * This constructor is used for test purposes + * This constructor is used for test purposes. */ public Feed(long id, String lastUpdate, String title, String link, String description, String paymentLink, String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl, @@ -173,56 +176,6 @@ public class Feed extends FeedFile { preferences = new FeedPreferences(0, true, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, username, password); } - public static Feed fromCursor(Cursor cursor) { - int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); - int indexLastUpdate = cursor.getColumnIndex(PodDBAdapter.KEY_LASTUPDATE); - int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE); - int indexCustomTitle = cursor.getColumnIndex(PodDBAdapter.KEY_CUSTOM_TITLE); - int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK); - int indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION); - int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK); - int indexAuthor = cursor.getColumnIndex(PodDBAdapter.KEY_AUTHOR); - int indexLanguage = cursor.getColumnIndex(PodDBAdapter.KEY_LANGUAGE); - int indexType = cursor.getColumnIndex(PodDBAdapter.KEY_TYPE); - int indexFeedIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_IDENTIFIER); - int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL); - int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL); - int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED); - int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED); - int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK); - int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE); - int indexSortOrder = cursor.getColumnIndex(PodDBAdapter.KEY_SORT_ORDER); - int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED); - int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL); - - Feed feed = new Feed( - cursor.getLong(indexId), - cursor.getString(indexLastUpdate), - cursor.getString(indexTitle), - cursor.getString(indexCustomTitle), - cursor.getString(indexLink), - cursor.getString(indexDescription), - cursor.getString(indexPaymentLink), - cursor.getString(indexAuthor), - cursor.getString(indexLanguage), - cursor.getString(indexType), - cursor.getString(indexFeedIdentifier), - cursor.getString(indexImageUrl), - cursor.getString(indexFileUrl), - cursor.getString(indexDownloadUrl), - cursor.getInt(indexDownloaded) > 0, - cursor.getInt(indexIsPaged) > 0, - cursor.getString(indexNextPageLink), - cursor.getString(indexHide), - SortOrder.fromCodeString(cursor.getString(indexSortOrder)), - cursor.getInt(indexLastUpdateFailed) > 0 - ); - - FeedPreferences preferences = FeedPreferences.fromCursor(cursor); - feed.setPreferences(preferences); - return feed; - } - /** * Returns the item at the specified index. * @@ -382,7 +335,7 @@ public class Feed extends FeedFile { } public void setCustomTitle(String customTitle) { - if(customTitle == null || customTitle.equals(feedTitle)) { + if (customTitle == null || customTitle.equals(feedTitle)) { this.customTitle = null; } else { this.customTitle = customTitle; 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 27f08243f..fcf61b070 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 @@ -23,6 +23,7 @@ import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.feed.SubscriptionsFilter; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.storage.mapper.FeedCursorMapper; import de.danoeh.antennapod.core.util.LongIntMap; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator; @@ -204,7 +205,7 @@ public final class DBReader { } private static Feed extractFeedFromCursorRow(Cursor cursor) { - Feed feed = Feed.fromCursor(cursor); + Feed feed = FeedCursorMapper.convert(cursor); FeedPreferences preferences = FeedPreferences.fromCursor(cursor); feed.setPreferences(preferences); return feed; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index f5a8fb07a..596ab624e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -19,6 +19,7 @@ import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.LocalFeedUpdater; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.storage.mapper.FeedCursorMapper; import de.danoeh.antennapod.core.sync.SyncService; import de.danoeh.antennapod.core.util.DownloadError; import de.danoeh.antennapod.core.util.LongList; @@ -516,7 +517,7 @@ public final class DBTasks { List items = new ArrayList<>(); if (cursor.moveToFirst()) { do { - items.add(Feed.fromCursor(cursor)); + items.add(FeedCursorMapper.convert(cursor)); } while (cursor.moveToNext()); } setResult(items); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index 8d1352a10..adb5e6a74 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -15,6 +15,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import de.danoeh.antennapod.core.storage.mapper.FeedItemFilterQuery; import org.apache.commons.io.FileUtils; @@ -372,6 +373,7 @@ public class PodDBAdapter { * For more information see * robolectric/robolectric#1890.

*/ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) public static void tearDownTests() { getInstance().dbHelper.close(); instance = null; @@ -1379,7 +1381,16 @@ public class PodDBAdapter { } /** - * Called when a database corruption happens + * Insert raw data to the database. * + * Call method only for unit tests. + */ + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public void insertTestData(@NonNull String table, @NonNull ContentValues values) { + db.insert(table, null, values); + } + + /** + * Called when a database corruption happens. */ public static class PodDbErrorHandler implements DatabaseErrorHandler { @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java new file mode 100644 index 000000000..783fba596 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapper.java @@ -0,0 +1,70 @@ +package de.danoeh.antennapod.core.storage.mapper; + +import android.database.Cursor; + +import androidx.annotation.NonNull; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedPreferences; +import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.danoeh.antennapod.core.util.SortOrder; + +/** + * Converts a {@link Cursor} to a {@link Feed} object. + */ +public abstract class FeedCursorMapper { + + /** + * Create a {@link Feed} instance from a database row (cursor). + */ + @NonNull + public static Feed convert(@NonNull Cursor cursor) { + int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); + int indexLastUpdate = cursor.getColumnIndex(PodDBAdapter.KEY_LASTUPDATE); + int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE); + int indexCustomTitle = cursor.getColumnIndex(PodDBAdapter.KEY_CUSTOM_TITLE); + int indexLink = cursor.getColumnIndex(PodDBAdapter.KEY_LINK); + int indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION); + int indexPaymentLink = cursor.getColumnIndex(PodDBAdapter.KEY_PAYMENT_LINK); + int indexAuthor = cursor.getColumnIndex(PodDBAdapter.KEY_AUTHOR); + int indexLanguage = cursor.getColumnIndex(PodDBAdapter.KEY_LANGUAGE); + int indexType = cursor.getColumnIndex(PodDBAdapter.KEY_TYPE); + int indexFeedIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_IDENTIFIER); + int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL); + int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL); + int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED); + int indexIsPaged = cursor.getColumnIndex(PodDBAdapter.KEY_IS_PAGED); + int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK); + int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE); + int indexSortOrder = cursor.getColumnIndex(PodDBAdapter.KEY_SORT_ORDER); + int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED); + int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL); + + Feed feed = new Feed( + cursor.getLong(indexId), + cursor.getString(indexLastUpdate), + cursor.getString(indexTitle), + cursor.getString(indexCustomTitle), + cursor.getString(indexLink), + cursor.getString(indexDescription), + cursor.getString(indexPaymentLink), + cursor.getString(indexAuthor), + cursor.getString(indexLanguage), + cursor.getString(indexType), + cursor.getString(indexFeedIdentifier), + cursor.getString(indexImageUrl), + cursor.getString(indexFileUrl), + cursor.getString(indexDownloadUrl), + cursor.getInt(indexDownloaded) > 0, + cursor.getInt(indexIsPaged) > 0, + cursor.getString(indexNextPageLink), + cursor.getString(indexHide), + SortOrder.fromCodeString(cursor.getString(indexSortOrder)), + cursor.getInt(indexLastUpdateFailed) > 0 + ); + + FeedPreferences preferences = FeedPreferences.fromCursor(cursor); + feed.setPreferences(preferences); + return feed; + } +} diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java new file mode 100644 index 000000000..c779b6d55 --- /dev/null +++ b/core/src/test/java/de/danoeh/antennapod/core/storage/mapper/FeedCursorMapperTest.java @@ -0,0 +1,100 @@ +package de.danoeh.antennapod.core.storage.mapper; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.storage.PodDBAdapter; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +@RunWith(RobolectricTestRunner.class) +public class FeedCursorMapperTest { + private PodDBAdapter adapter; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + + PodDBAdapter.init(context); + adapter = PodDBAdapter.getInstance(); + + writeFeedToDatabase(); + } + + @After + public void tearDown() { + PodDBAdapter.tearDownTests(); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void testFromCursor() { + try (Cursor cursor = adapter.getAllFeedsCursor()) { + cursor.moveToNext(); + Feed feed = FeedCursorMapper.convert(cursor); + assertTrue(feed.getId() >= 0); + assertEquals("feed custom title", feed.getTitle()); + assertEquals("feed custom title", feed.getCustomTitle()); + assertEquals("feed link", feed.getLink()); + assertEquals("feed description", feed.getDescription()); + assertEquals("feed payment link", feed.getPaymentLink()); + assertEquals("feed author", feed.getAuthor()); + assertEquals("feed language", feed.getLanguage()); + assertEquals("feed image url", feed.getImageUrl()); + assertEquals("feed file url", feed.getFile_url()); + assertEquals("feed download url", feed.getDownload_url()); + assertTrue(feed.isDownloaded()); + assertEquals("feed last update", feed.getLastUpdate()); + assertEquals("feed type", feed.getType()); + assertEquals("feed identifier", feed.getFeedIdentifier()); + assertTrue(feed.isPaged()); + assertEquals("feed next page link", feed.getNextPageLink()); + assertTrue(feed.getItemFilter().showUnplayed); + assertEquals(1, feed.getSortOrder().code); + assertTrue(feed.hasLastUpdateFailed()); + } + } + + /** + * Insert test data to the database. + * Uses raw database insert instead of adapter.setCompleteFeed() to avoid testing the Feed class + * against itself. + */ + private void writeFeedToDatabase() { + ContentValues values = new ContentValues(); + values.put(PodDBAdapter.KEY_TITLE, "feed title"); + values.put(PodDBAdapter.KEY_CUSTOM_TITLE, "feed custom title"); + values.put(PodDBAdapter.KEY_LINK, "feed link"); + values.put(PodDBAdapter.KEY_DESCRIPTION, "feed description"); + values.put(PodDBAdapter.KEY_PAYMENT_LINK, "feed payment link"); + values.put(PodDBAdapter.KEY_AUTHOR, "feed author"); + values.put(PodDBAdapter.KEY_LANGUAGE, "feed language"); + values.put(PodDBAdapter.KEY_IMAGE_URL, "feed image url"); + + values.put(PodDBAdapter.KEY_FILE_URL, "feed file url"); + values.put(PodDBAdapter.KEY_DOWNLOAD_URL, "feed download url"); + values.put(PodDBAdapter.KEY_DOWNLOADED, true); + values.put(PodDBAdapter.KEY_LASTUPDATE, "feed last update"); + values.put(PodDBAdapter.KEY_TYPE, "feed type"); + values.put(PodDBAdapter.KEY_FEED_IDENTIFIER, "feed identifier"); + + values.put(PodDBAdapter.KEY_IS_PAGED, true); + values.put(PodDBAdapter.KEY_NEXT_PAGE_LINK, "feed next page link"); + values.put(PodDBAdapter.KEY_HIDE, "unplayed"); + values.put(PodDBAdapter.KEY_SORT_ORDER, "1"); + values.put(PodDBAdapter.KEY_LAST_UPDATE_FAILED, true); + + adapter.insertTestData(PodDBAdapter.TABLE_NAME_FEEDS, values); + } +}