Refactoring: Remove Cursor related code from Feed class (#4968)

This commit is contained in:
Herbert Reiter 2021-02-28 14:16:07 +01:00 committed by GitHub
parent 6e76dcea3d
commit 47a0336c3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 201 additions and 65 deletions

View File

@ -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<FeedItem> 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;

View File

@ -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;

View File

@ -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<Feed> items = new ArrayList<>();
if (cursor.moveToFirst()) {
do {
items.add(Feed.fromCursor(cursor));
items.add(FeedCursorMapper.convert(cursor));
} while (cursor.moveToNext());
}
setResult(items);

View File

@ -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
* <a href="https://github.com/robolectric/robolectric/issues/1890">robolectric/robolectric#1890</a>.</p>
*/
@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

View File

@ -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;
}
}

View File

@ -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);
}
}