Convert playback history fragment to lazy loading (#5886)
This commit is contained in:
parent
fd066a648b
commit
df53c5bfe5
|
@ -49,9 +49,9 @@ public class InboxFragment extends EpisodesListFragment implements Toolbar.OnMen
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View inboxContainer = View.inflate(getContext(), R.layout.inbox_fragment, null);
|
View inboxContainer = View.inflate(getContext(), R.layout.list_container_fragment, null);
|
||||||
View root = super.onCreateView(inflater, container, savedInstanceState);
|
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
((FrameLayout) inboxContainer.findViewById(R.id.inboxContent)).addView(root);
|
((FrameLayout) inboxContainer.findViewById(R.id.listContent)).addView(root);
|
||||||
emptyView.setTitle(R.string.no_inbox_head_label);
|
emptyView.setTitle(R.string.no_inbox_head_label);
|
||||||
emptyView.setMessage(R.string.no_inbox_label);
|
emptyView.setMessage(R.string.no_inbox_label);
|
||||||
|
|
||||||
|
|
|
@ -1,57 +1,36 @@
|
||||||
package de.danoeh.antennapod.fragment;
|
package de.danoeh.antennapod.fragment;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import de.danoeh.antennapod.R;
|
import de.danoeh.antennapod.R;
|
||||||
import de.danoeh.antennapod.activity.MainActivity;
|
import de.danoeh.antennapod.activity.MainActivity;
|
||||||
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
|
import de.danoeh.antennapod.adapter.EpisodeItemListAdapter;
|
||||||
import de.danoeh.antennapod.core.event.DownloadEvent;
|
|
||||||
import de.danoeh.antennapod.core.event.DownloaderUpdate;
|
|
||||||
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
|
import de.danoeh.antennapod.core.menuhandler.MenuItemUtils;
|
||||||
import de.danoeh.antennapod.event.FeedItemEvent;
|
|
||||||
import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent;
|
import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent;
|
||||||
import de.danoeh.antennapod.event.playback.PlaybackPositionEvent;
|
|
||||||
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
||||||
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
|
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
|
||||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||||
import de.danoeh.antennapod.core.storage.DBReader;
|
import de.danoeh.antennapod.core.storage.DBReader;
|
||||||
import de.danoeh.antennapod.core.storage.DBWriter;
|
import de.danoeh.antennapod.core.storage.DBWriter;
|
||||||
import de.danoeh.antennapod.core.util.FeedItemUtil;
|
|
||||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
|
||||||
import de.danoeh.antennapod.view.EmptyViewHandler;
|
|
||||||
import de.danoeh.antennapod.view.EpisodeItemListRecyclerView;
|
|
||||||
import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuItemClickListener {
|
public class PlaybackHistoryFragment extends EpisodesListFragment implements Toolbar.OnMenuItemClickListener {
|
||||||
public static final String TAG = "PlaybackHistoryFragment";
|
public static final String TAG = "PlaybackHistoryFragment";
|
||||||
private static final String KEY_UP_ARROW = "up_arrow";
|
private static final String KEY_UP_ARROW = "up_arrow";
|
||||||
|
|
||||||
private List<FeedItem> playbackHistory;
|
|
||||||
private PlaybackHistoryListAdapter adapter;
|
|
||||||
private Disposable disposable;
|
|
||||||
private EpisodeItemListRecyclerView recyclerView;
|
|
||||||
private EmptyViewHandler emptyView;
|
|
||||||
private ProgressBar progressBar;
|
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private boolean displayUpArrow;
|
private boolean displayUpArrow;
|
||||||
|
|
||||||
|
@ -64,8 +43,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
@Nullable Bundle savedInstanceState) {
|
@Nullable Bundle savedInstanceState) {
|
||||||
View root = inflater.inflate(R.layout.simple_list_fragment, container, false);
|
View historyContainer = View.inflate(getContext(), R.layout.list_container_fragment, null);
|
||||||
toolbar = root.findViewById(R.id.toolbar);
|
View root = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
|
||||||
|
((FrameLayout) historyContainer.findViewById(R.id.listContent)).addView(root);
|
||||||
|
|
||||||
|
toolbar = historyContainer.findViewById(R.id.toolbar);
|
||||||
toolbar.setTitle(R.string.playback_history_label);
|
toolbar.setTitle(R.string.playback_history_label);
|
||||||
toolbar.setOnMenuItemClickListener(this);
|
toolbar.setOnMenuItemClickListener(this);
|
||||||
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
displayUpArrow = getParentFragmentManager().getBackStackEntryCount() != 0;
|
||||||
|
@ -76,34 +59,14 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||||
toolbar.inflateMenu(R.menu.playback_history);
|
toolbar.inflateMenu(R.menu.playback_history);
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
|
|
||||||
recyclerView = root.findViewById(R.id.recyclerView);
|
listAdapter = new PlaybackHistoryListAdapter((MainActivity) getActivity());
|
||||||
recyclerView.setRecycledViewPool(((MainActivity) getActivity()).getRecycledViewPool());
|
recyclerView.setAdapter(listAdapter);
|
||||||
adapter = new PlaybackHistoryListAdapter((MainActivity) getActivity());
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
progressBar = root.findViewById(R.id.progLoading);
|
|
||||||
|
|
||||||
emptyView = new EmptyViewHandler(getActivity());
|
|
||||||
emptyView.setIcon(R.drawable.ic_history);
|
emptyView.setIcon(R.drawable.ic_history);
|
||||||
emptyView.setTitle(R.string.no_history_head_label);
|
emptyView.setTitle(R.string.no_history_head_label);
|
||||||
emptyView.setMessage(R.string.no_history_label);
|
emptyView.setMessage(R.string.no_history_label);
|
||||||
emptyView.attachToRecyclerView(recyclerView);
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return historyContainer;
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
EventBus.getDefault().register(this);
|
|
||||||
loadItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
EventBus.getDefault().unregister(this);
|
|
||||||
if (disposable != null) {
|
|
||||||
disposable.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,55 +75,8 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEventMainThread(FeedItemEvent event) {
|
|
||||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
|
||||||
if (playbackHistory == null) {
|
|
||||||
return;
|
|
||||||
} else if (adapter == null) {
|
|
||||||
loadItems();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (int i = 0, size = event.items.size(); i < size; i++) {
|
|
||||||
FeedItem item = event.items.get(i);
|
|
||||||
int pos = FeedItemUtil.indexOfItemWithId(playbackHistory, item.getId());
|
|
||||||
if (pos >= 0) {
|
|
||||||
playbackHistory.remove(pos);
|
|
||||||
playbackHistory.add(pos, item);
|
|
||||||
adapter.notifyItemChangedCompat(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEventMainThread(DownloadEvent event) {
|
|
||||||
Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]");
|
|
||||||
DownloaderUpdate update = event.update;
|
|
||||||
if (adapter != null && update.mediaIds.length > 0) {
|
|
||||||
for (long mediaId : update.mediaIds) {
|
|
||||||
int pos = FeedItemUtil.indexOfItemWithMediaId(playbackHistory, mediaId);
|
|
||||||
if (pos >= 0) {
|
|
||||||
adapter.notifyItemChangedCompat(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
|
||||||
if (adapter != null) {
|
|
||||||
for (int i = 0; i < adapter.getItemCount(); i++) {
|
|
||||||
EpisodeItemViewHolder holder = (EpisodeItemViewHolder) recyclerView.findViewHolderForAdapterPosition(i);
|
|
||||||
if (holder != null && holder.isCurrentlyPlayingItem()) {
|
|
||||||
holder.notifyPlaybackPositionUpdated(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshToolbarState() {
|
public void refreshToolbarState() {
|
||||||
boolean hasHistory = playbackHistory != null && !playbackHistory.isEmpty();
|
boolean hasHistory = episodes != null && !episodes.isEmpty();
|
||||||
toolbar.getMenu().findItem(R.id.clear_history_item).setVisible(hasHistory);
|
toolbar.getMenu().findItem(R.id.clear_history_item).setVisible(hasHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,33 +89,6 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onContextItemSelected(@NonNull MenuItem item) {
|
|
||||||
FeedItem selectedItem = adapter.getLongPressedItem();
|
|
||||||
if (selectedItem == null) {
|
|
||||||
Log.i(TAG, "Selected item at current position was null, ignoring selection");
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
|
||||||
return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), selectedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
||||||
public void onKeyUp(KeyEvent event) {
|
|
||||||
if (!isAdded() || !isVisible() || !isMenuVisible()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (event.getKeyCode()) {
|
|
||||||
case KeyEvent.KEYCODE_T:
|
|
||||||
recyclerView.smoothScrollToPosition(0);
|
|
||||||
break;
|
|
||||||
case KeyEvent.KEYCODE_B:
|
|
||||||
recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
public void onHistoryUpdated(PlaybackHistoryEvent event) {
|
public void onHistoryUpdated(PlaybackHistoryEvent event) {
|
||||||
loadItems();
|
loadItems();
|
||||||
|
@ -212,59 +101,47 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
|
public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) {
|
||||||
loadItems();
|
loadItems();
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFragmentLoaded() {
|
@Override
|
||||||
adapter.notifyDataSetChanged();
|
protected void onFragmentLoaded(List<FeedItem> episodes) {
|
||||||
|
super.onFragmentLoaded(episodes);
|
||||||
|
listAdapter.notifyDataSetChanged();
|
||||||
refreshToolbarState();
|
refreshToolbarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadItems() {
|
|
||||||
if (disposable != null) {
|
|
||||||
disposable.dispose();
|
|
||||||
}
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
emptyView.hide();
|
|
||||||
disposable = Observable.fromCallable(this::loadData)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(result -> {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
playbackHistory = result;
|
|
||||||
adapter.updateItems(playbackHistory);
|
|
||||||
onFragmentLoaded();
|
|
||||||
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private List<FeedItem> loadData() {
|
|
||||||
List<FeedItem> history = DBReader.getPlaybackHistory();
|
|
||||||
DBReader.loadAdditionalFeedItemListData(history);
|
|
||||||
return history;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PlaybackHistoryListAdapter extends EpisodeItemListAdapter {
|
private class PlaybackHistoryListAdapter extends EpisodeItemListAdapter {
|
||||||
|
|
||||||
public PlaybackHistoryListAdapter(MainActivity mainActivity) {
|
public PlaybackHistoryListAdapter(MainActivity mainActivity) {
|
||||||
super(mainActivity);
|
super(mainActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void afterBindViewHolder(EpisodeItemViewHolder holder, int pos) {
|
|
||||||
// played items shouldn't be transparent for this fragment since, *all* items
|
|
||||||
// in this fragment will, by definition, be played. So it serves no purpose and can make
|
|
||||||
// it harder to read.
|
|
||||||
holder.itemView.setAlpha(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||||
super.onCreateContextMenu(menu, v, menuInfo);
|
super.onCreateContextMenu(menu, v, menuInfo);
|
||||||
MenuItemUtils.setOnClickListeners(menu, PlaybackHistoryFragment.this::onContextItemSelected);
|
MenuItemUtils.setOnClickListeners(menu, PlaybackHistoryFragment.this::onContextItemSelected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected List<FeedItem> loadData() {
|
||||||
|
return DBReader.getPlaybackHistory(0, page * EPISODES_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected List<FeedItem> loadMoreData(int page) {
|
||||||
|
return DBReader.getPlaybackHistory((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int loadTotalItemCount() {
|
||||||
|
return (int) DBReader.getPlaybackHistoryLength();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,13 @@
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
android:minHeight="?attr/actionBarSize"
|
android:minHeight="?attr/actionBarSize"
|
||||||
android:theme="?attr/actionBarTheme"
|
android:theme="?attr/actionBarTheme"
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
app:title="@string/inbox_label" />
|
app:title="@string/inbox_label" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/inboxContent"
|
android:id="@+id/listContent"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@+id/toolbar" />
|
android:layout_below="@+id/toolbar" />
|
|
@ -45,11 +45,6 @@ public final class DBReader {
|
||||||
|
|
||||||
private static final String TAG = "DBReader";
|
private static final String TAG = "DBReader";
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum size of the list returned by {@link #getPlaybackHistory()}.
|
|
||||||
*/
|
|
||||||
public static final int PLAYBACK_HISTORY_SIZE = 50;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum size of the list returned by {@link #getDownloadLog()}.
|
* Maximum size of the list returned by {@link #getDownloadLog()}.
|
||||||
*/
|
*/
|
||||||
|
@ -419,11 +414,12 @@ public final class DBReader {
|
||||||
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
|
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
|
||||||
* has been completed at least once.
|
* has been completed at least once.
|
||||||
*
|
*
|
||||||
|
* @param limit The maximum number of items to return.
|
||||||
|
*
|
||||||
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
|
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
|
||||||
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static List<FeedItem> getPlaybackHistory() {
|
public static List<FeedItem> getPlaybackHistory(int offset, int limit) {
|
||||||
Log.d(TAG, "getPlaybackHistory() called");
|
Log.d(TAG, "getPlaybackHistory() called");
|
||||||
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
@ -432,7 +428,7 @@ public final class DBReader {
|
||||||
Cursor mediaCursor = null;
|
Cursor mediaCursor = null;
|
||||||
Cursor itemCursor = null;
|
Cursor itemCursor = null;
|
||||||
try {
|
try {
|
||||||
mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
|
mediaCursor = adapter.getCompletedMediaCursor(offset, limit);
|
||||||
String[] itemIds = new String[mediaCursor.getCount()];
|
String[] itemIds = new String[mediaCursor.getCount()];
|
||||||
for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
|
for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
|
||||||
int index = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
|
int index = mediaCursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
|
||||||
|
@ -454,6 +450,17 @@ public final class DBReader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long getPlaybackHistoryLength() {
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return adapter.getCompletedMediaLength();
|
||||||
|
} finally {
|
||||||
|
adapter.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the download log from the database.
|
* Loads the download log from the database.
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.storage;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -16,8 +18,11 @@ import de.danoeh.antennapod.core.util.LongList;
|
||||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.runners.Enclosed;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
||||||
import static de.danoeh.antennapod.core.storage.DbTestUtils.saveFeedlist;
|
import static de.danoeh.antennapod.core.storage.DbTestUtils.saveFeedlist;
|
||||||
|
@ -31,410 +36,473 @@ import static org.junit.Assert.assertTrue;
|
||||||
* Test class for DBReader.
|
* Test class for DBReader.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(Enclosed.class)
|
||||||
public class DbReaderTest {
|
public class DbReaderTest {
|
||||||
|
@Ignore("Not a test")
|
||||||
|
public static class TestBase {
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||||
|
UserPreferences.init(context);
|
||||||
|
|
||||||
@Before
|
PodDBAdapter.init(context);
|
||||||
public void setUp() {
|
PodDBAdapter.deleteDatabase();
|
||||||
Context context = InstrumentationRegistry.getInstrumentation().getContext();
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
UserPreferences.init(context);
|
adapter.open();
|
||||||
|
adapter.close();
|
||||||
|
}
|
||||||
|
|
||||||
PodDBAdapter.init(context);
|
@After
|
||||||
PodDBAdapter.deleteDatabase();
|
public void tearDown() {
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
PodDBAdapter.tearDownTests();
|
||||||
adapter.open();
|
DBWriter.tearDownTests();
|
||||||
adapter.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
PodDBAdapter.tearDownTests();
|
|
||||||
DBWriter.tearDownTests();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetFeedList() {
|
|
||||||
List<Feed> feeds = saveFeedlist(10, 0, false);
|
|
||||||
List<Feed> savedFeeds = DBReader.getFeedList();
|
|
||||||
assertNotNull(savedFeeds);
|
|
||||||
assertEquals(feeds.size(), savedFeeds.size());
|
|
||||||
for (int i = 0; i < feeds.size(); i++) {
|
|
||||||
assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public void testGetFeedListSortOrder() {
|
public static class SingleTests extends TestBase {
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
@Test
|
||||||
adapter.open();
|
public void testGetFeedList() {
|
||||||
|
List<Feed> feeds = saveFeedlist(10, 0, false);
|
||||||
Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", true);
|
List<Feed> savedFeeds = DBReader.getFeedList();
|
||||||
Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", true);
|
assertNotNull(savedFeeds);
|
||||||
Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", true);
|
assertEquals(feeds.size(), savedFeeds.size());
|
||||||
Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", true);
|
for (int i = 0; i < feeds.size(); i++) {
|
||||||
adapter.setCompleteFeed(feed1);
|
assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
|
||||||
adapter.setCompleteFeed(feed2);
|
|
||||||
adapter.setCompleteFeed(feed3);
|
|
||||||
adapter.setCompleteFeed(feed4);
|
|
||||||
assertTrue(feed1.getId() != 0);
|
|
||||||
assertTrue(feed2.getId() != 0);
|
|
||||||
assertTrue(feed3.getId() != 0);
|
|
||||||
assertTrue(feed4.getId() != 0);
|
|
||||||
|
|
||||||
adapter.close();
|
|
||||||
|
|
||||||
List<Feed> saved = DBReader.getFeedList();
|
|
||||||
assertNotNull(saved);
|
|
||||||
assertEquals("Wrong size: ", 4, saved.size());
|
|
||||||
|
|
||||||
assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
|
|
||||||
assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
|
|
||||||
assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
|
|
||||||
assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFeedListDownloadUrls() {
|
|
||||||
List<Feed> feeds = saveFeedlist(10, 0, false);
|
|
||||||
List<String> urls = DBReader.getFeedListDownloadUrls();
|
|
||||||
assertNotNull(urls);
|
|
||||||
assertEquals(feeds.size(), urls.size());
|
|
||||||
for (int i = 0; i < urls.size(); i++) {
|
|
||||||
assertEquals(urls.get(i), feeds.get(i).getDownload_url());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLoadFeedDataOfFeedItemlist() {
|
|
||||||
final int numFeeds = 10;
|
|
||||||
final int numItems = 1;
|
|
||||||
List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
for (Feed f : feeds) {
|
|
||||||
for (FeedItem item : f.getItems()) {
|
|
||||||
item.setFeed(null);
|
|
||||||
item.setFeedId(f.getId());
|
|
||||||
items.add(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBReader.loadAdditionalFeedItemListData(items);
|
|
||||||
for (int i = 0; i < numFeeds; i++) {
|
@Test
|
||||||
for (int j = 0; j < numItems; j++) {
|
public void testGetFeedListSortOrder() {
|
||||||
FeedItem item = feeds.get(i).getItems().get(j);
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
assertNotNull(item.getFeed());
|
adapter.open();
|
||||||
assertEquals(feeds.get(i).getId(), item.getFeed().getId());
|
|
||||||
assertEquals(item.getFeed().getId(), item.getFeedId());
|
Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", true);
|
||||||
|
Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", true);
|
||||||
|
Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", true);
|
||||||
|
Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", true);
|
||||||
|
adapter.setCompleteFeed(feed1);
|
||||||
|
adapter.setCompleteFeed(feed2);
|
||||||
|
adapter.setCompleteFeed(feed3);
|
||||||
|
adapter.setCompleteFeed(feed4);
|
||||||
|
assertTrue(feed1.getId() != 0);
|
||||||
|
assertTrue(feed2.getId() != 0);
|
||||||
|
assertTrue(feed3.getId() != 0);
|
||||||
|
assertTrue(feed4.getId() != 0);
|
||||||
|
|
||||||
|
adapter.close();
|
||||||
|
|
||||||
|
List<Feed> saved = DBReader.getFeedList();
|
||||||
|
assertNotNull(saved);
|
||||||
|
assertEquals("Wrong size: ", 4, saved.size());
|
||||||
|
|
||||||
|
assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
|
||||||
|
assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
|
||||||
|
assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
|
||||||
|
assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFeedListDownloadUrls() {
|
||||||
|
List<Feed> feeds = saveFeedlist(10, 0, false);
|
||||||
|
List<String> urls = DBReader.getFeedListDownloadUrls();
|
||||||
|
assertNotNull(urls);
|
||||||
|
assertEquals(feeds.size(), urls.size());
|
||||||
|
for (int i = 0; i < urls.size(); i++) {
|
||||||
|
assertEquals(urls.get(i), feeds.get(i).getDownload_url());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetFeedItemList() {
|
public void testLoadFeedDataOfFeedItemlist() {
|
||||||
final int numFeeds = 1;
|
final int numFeeds = 10;
|
||||||
final int numItems = 10;
|
final int numItems = 1;
|
||||||
Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
|
List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
|
||||||
List<FeedItem> items = feed.getItems();
|
List<FeedItem> items = new ArrayList<>();
|
||||||
feed.setItems(null);
|
for (Feed f : feeds) {
|
||||||
List<FeedItem> savedItems = DBReader.getFeedItemList(feed);
|
for (FeedItem item : f.getItems()) {
|
||||||
assertNotNull(savedItems);
|
item.setFeed(null);
|
||||||
assertEquals(items.size(), savedItems.size());
|
item.setFeedId(f.getId());
|
||||||
for (int i = 0; i < savedItems.size(); i++) {
|
items.add(item);
|
||||||
assertEquals(savedItems.get(i).getId(), items.get(i).getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private List<FeedItem> saveQueue(int numItems) {
|
|
||||||
if (numItems <= 0) {
|
|
||||||
throw new IllegalArgumentException("numItems<=0");
|
|
||||||
}
|
|
||||||
List<Feed> feeds = saveFeedlist(numItems, numItems, false);
|
|
||||||
List<FeedItem> allItems = new ArrayList<>();
|
|
||||||
for (Feed f : feeds) {
|
|
||||||
allItems.addAll(f.getItems());
|
|
||||||
}
|
|
||||||
// take random items from every feed
|
|
||||||
Random random = new Random();
|
|
||||||
List<FeedItem> queue = new ArrayList<>();
|
|
||||||
while (queue.size() < numItems) {
|
|
||||||
int index = random.nextInt(numItems);
|
|
||||||
if (!queue.contains(allItems.get(index))) {
|
|
||||||
queue.add(allItems.get(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
|
||||||
adapter.open();
|
|
||||||
adapter.setQueue(queue);
|
|
||||||
adapter.close();
|
|
||||||
return queue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetQueueIdList() {
|
|
||||||
final int numItems = 10;
|
|
||||||
List<FeedItem> queue = saveQueue(numItems);
|
|
||||||
LongList ids = DBReader.getQueueIDList();
|
|
||||||
assertNotNull(ids);
|
|
||||||
assertEquals(ids.size(), queue.size());
|
|
||||||
for (int i = 0; i < queue.size(); i++) {
|
|
||||||
assertTrue(ids.get(i) != 0);
|
|
||||||
assertEquals(ids.get(i), queue.get(i).getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetQueue() {
|
|
||||||
final int numItems = 10;
|
|
||||||
List<FeedItem> queue = saveQueue(numItems);
|
|
||||||
List<FeedItem> savedQueue = DBReader.getQueue();
|
|
||||||
assertNotNull(savedQueue);
|
|
||||||
assertEquals(savedQueue.size(), queue.size());
|
|
||||||
for (int i = 0; i < queue.size(); i++) {
|
|
||||||
assertTrue(savedQueue.get(i).getId() != 0);
|
|
||||||
assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private List<FeedItem> saveDownloadedItems(int numItems) {
|
|
||||||
if (numItems <= 0) {
|
|
||||||
throw new IllegalArgumentException("numItems<=0");
|
|
||||||
}
|
|
||||||
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
for (Feed f : feeds) {
|
|
||||||
items.addAll(f.getItems());
|
|
||||||
}
|
|
||||||
List<FeedItem> downloaded = new ArrayList<>();
|
|
||||||
Random random = new Random();
|
|
||||||
|
|
||||||
while (downloaded.size() < numItems) {
|
|
||||||
int i = random.nextInt(numItems);
|
|
||||||
if (!downloaded.contains(items.get(i))) {
|
|
||||||
FeedItem item = items.get(i);
|
|
||||||
item.getMedia().setDownloaded(true);
|
|
||||||
item.getMedia().setFile_url("file" + i);
|
|
||||||
downloaded.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
|
||||||
adapter.open();
|
|
||||||
adapter.storeFeedItemlist(downloaded);
|
|
||||||
adapter.close();
|
|
||||||
return downloaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetDownloadedItems() {
|
|
||||||
final int numItems = 10;
|
|
||||||
List<FeedItem> downloaded = saveDownloadedItems(numItems);
|
|
||||||
List<FeedItem> downloadedSaved = DBReader.getDownloadedItems();
|
|
||||||
assertNotNull(downloadedSaved);
|
|
||||||
assertEquals(downloaded.size(), downloadedSaved.size());
|
|
||||||
for (FeedItem item : downloadedSaved) {
|
|
||||||
assertNotNull(item.getMedia());
|
|
||||||
assertTrue(item.getMedia().isDownloaded());
|
|
||||||
assertNotNull(item.getMedia().getDownload_url());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
|
||||||
private List<FeedItem> saveNewItems(int numItems) {
|
|
||||||
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
|
|
||||||
List<FeedItem> items = new ArrayList<>();
|
|
||||||
for (Feed f : feeds) {
|
|
||||||
items.addAll(f.getItems());
|
|
||||||
}
|
|
||||||
List<FeedItem> newItems = new ArrayList<>();
|
|
||||||
Random random = new Random();
|
|
||||||
|
|
||||||
while (newItems.size() < numItems) {
|
|
||||||
int i = random.nextInt(numItems);
|
|
||||||
if (!newItems.contains(items.get(i))) {
|
|
||||||
FeedItem item = items.get(i);
|
|
||||||
item.setNew();
|
|
||||||
newItems.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
|
||||||
adapter.open();
|
|
||||||
adapter.storeFeedItemlist(newItems);
|
|
||||||
adapter.close();
|
|
||||||
return newItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetNewItemIds() {
|
|
||||||
final int numItems = 10;
|
|
||||||
|
|
||||||
List<FeedItem> newItems = saveNewItems(numItems);
|
|
||||||
long[] unreadIds = new long[newItems.size()];
|
|
||||||
for (int i = 0; i < newItems.size(); i++) {
|
|
||||||
unreadIds[i] = newItems.get(i).getId();
|
|
||||||
}
|
|
||||||
List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
|
|
||||||
assertNotNull(newItemsSaved);
|
|
||||||
assertEquals(newItemsSaved.size(), newItems.size());
|
|
||||||
for (FeedItem feedItem : newItemsSaved) {
|
|
||||||
long savedId = feedItem.getId();
|
|
||||||
boolean found = false;
|
|
||||||
for (long id : unreadIds) {
|
|
||||||
if (id == savedId) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertTrue(found);
|
DBReader.loadAdditionalFeedItemListData(items);
|
||||||
}
|
for (int i = 0; i < numFeeds; i++) {
|
||||||
}
|
for (int j = 0; j < numItems; j++) {
|
||||||
|
FeedItem item = feeds.get(i).getItems().get(j);
|
||||||
@Test
|
assertNotNull(item.getFeed());
|
||||||
public void testGetPlaybackHistory() {
|
assertEquals(feeds.get(i).getId(), item.getFeed().getId());
|
||||||
final int numItems = (DBReader.PLAYBACK_HISTORY_SIZE + 1) * 2;
|
assertEquals(item.getFeed().getId(), item.getFeedId());
|
||||||
final int playedItems = DBReader.PLAYBACK_HISTORY_SIZE + 1;
|
}
|
||||||
final int numReturnedItems = Math.min(playedItems, DBReader.PLAYBACK_HISTORY_SIZE);
|
|
||||||
final int numFeeds = 1;
|
|
||||||
|
|
||||||
Feed feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
|
|
||||||
long[] ids = new long[playedItems];
|
|
||||||
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
|
||||||
adapter.open();
|
|
||||||
for (int i = 0; i < playedItems; i++) {
|
|
||||||
FeedMedia m = feed.getItems().get(i).getMedia();
|
|
||||||
m.setPlaybackCompletionDate(new Date(i + 1));
|
|
||||||
adapter.setFeedMediaPlaybackCompletionDate(m);
|
|
||||||
ids[ids.length - 1 - i] = m.getItem().getId();
|
|
||||||
}
|
|
||||||
adapter.close();
|
|
||||||
|
|
||||||
List<FeedItem> saved = DBReader.getPlaybackHistory();
|
|
||||||
assertNotNull(saved);
|
|
||||||
assertEquals("Wrong size: ", numReturnedItems, saved.size());
|
|
||||||
for (int i = 0; i < numReturnedItems; i++) {
|
|
||||||
FeedItem item = saved.get(i);
|
|
||||||
assertNotNull(item.getMedia().getPlaybackCompletionDate());
|
|
||||||
assertEquals("Wrong sort order: ", item.getId(), ids[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
|
|
||||||
final int numFeeds = 10;
|
|
||||||
final int numItems = 10;
|
|
||||||
DbTestUtils.saveFeedlist(numFeeds, numItems, true);
|
|
||||||
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
|
|
||||||
assertEquals(numFeeds, navDrawerData.items.size());
|
|
||||||
assertEquals(0, navDrawerData.numNewItems);
|
|
||||||
assertEquals(0, navDrawerData.queueSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
|
|
||||||
final int numFeeds = 10;
|
|
||||||
final int numItems = 10;
|
|
||||||
final int numQueue = 1;
|
|
||||||
final int numNew = 2;
|
|
||||||
List<Feed> feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true);
|
|
||||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
|
||||||
adapter.open();
|
|
||||||
for (int i = 0; i < numNew; i++) {
|
|
||||||
FeedItem item = feeds.get(0).getItems().get(i);
|
|
||||||
item.setNew();
|
|
||||||
adapter.setSingleFeedItem(item);
|
|
||||||
}
|
|
||||||
List<FeedItem> queue = new ArrayList<>();
|
|
||||||
for (int i = 0; i < numQueue; i++) {
|
|
||||||
FeedItem item = feeds.get(1).getItems().get(i);
|
|
||||||
queue.add(item);
|
|
||||||
}
|
|
||||||
adapter.setQueue(queue);
|
|
||||||
|
|
||||||
adapter.close();
|
|
||||||
|
|
||||||
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
|
|
||||||
assertEquals(numFeeds, navDrawerData.items.size());
|
|
||||||
assertEquals(numNew, navDrawerData.numNewItems);
|
|
||||||
assertEquals(numQueue, navDrawerData.queueSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetFeedItemlistCheckChaptersFalse() {
|
|
||||||
List<Feed> feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0);
|
|
||||||
for (Feed feed : feeds) {
|
|
||||||
for (FeedItem item : feed.getItems()) {
|
|
||||||
assertFalse(item.hasChapters());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetFeedItemlistCheckChaptersTrue() {
|
public void testGetFeedItemList() {
|
||||||
List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
|
final int numFeeds = 1;
|
||||||
for (Feed feed : feeds) {
|
final int numItems = 10;
|
||||||
for (FeedItem item : feed.getItems()) {
|
Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
|
||||||
assertTrue(item.hasChapters());
|
List<FeedItem> items = feed.getItems();
|
||||||
|
feed.setItems(null);
|
||||||
|
List<FeedItem> savedItems = DBReader.getFeedItemList(feed);
|
||||||
|
assertNotNull(savedItems);
|
||||||
|
assertEquals(items.size(), savedItems.size());
|
||||||
|
for (int i = 0; i < savedItems.size(); i++) {
|
||||||
|
assertEquals(savedItems.get(i).getId(), items.get(i).getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@SuppressWarnings("SameParameterValue")
|
||||||
public void testLoadChaptersOfFeedItemNoChapters() {
|
private List<FeedItem> saveQueue(int numItems) {
|
||||||
List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
|
if (numItems <= 0) {
|
||||||
saveFeedlist(1, 3, false, true, 3);
|
throw new IllegalArgumentException("numItems<=0");
|
||||||
for (Feed feed : feeds) {
|
}
|
||||||
for (FeedItem item : feed.getItems()) {
|
List<Feed> feeds = saveFeedlist(numItems, numItems, false);
|
||||||
assertFalse(item.hasChapters());
|
List<FeedItem> allItems = new ArrayList<>();
|
||||||
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
for (Feed f : feeds) {
|
||||||
assertFalse(item.hasChapters());
|
allItems.addAll(f.getItems());
|
||||||
assertNull(item.getChapters());
|
}
|
||||||
|
// take random items from every feed
|
||||||
|
Random random = new Random();
|
||||||
|
List<FeedItem> queue = new ArrayList<>();
|
||||||
|
while (queue.size() < numItems) {
|
||||||
|
int index = random.nextInt(numItems);
|
||||||
|
if (!queue.contains(allItems.get(index))) {
|
||||||
|
queue.add(allItems.get(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
adapter.setQueue(queue);
|
||||||
|
adapter.close();
|
||||||
|
return queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetQueueIdList() {
|
||||||
|
final int numItems = 10;
|
||||||
|
List<FeedItem> queue = saveQueue(numItems);
|
||||||
|
LongList ids = DBReader.getQueueIDList();
|
||||||
|
assertNotNull(ids);
|
||||||
|
assertEquals(ids.size(), queue.size());
|
||||||
|
for (int i = 0; i < queue.size(); i++) {
|
||||||
|
assertTrue(ids.get(i) != 0);
|
||||||
|
assertEquals(ids.get(i), queue.get(i).getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLoadChaptersOfFeedItemWithChapters() {
|
public void testGetQueue() {
|
||||||
final int numChapters = 3;
|
final int numItems = 10;
|
||||||
DbTestUtils.saveFeedlist(1, 3, false, false, 0);
|
List<FeedItem> queue = saveQueue(numItems);
|
||||||
List<Feed> feeds = saveFeedlist(1, 3, false, true, numChapters);
|
List<FeedItem> savedQueue = DBReader.getQueue();
|
||||||
for (Feed feed : feeds) {
|
assertNotNull(savedQueue);
|
||||||
for (FeedItem item : feed.getItems()) {
|
assertEquals(savedQueue.size(), queue.size());
|
||||||
assertTrue(item.hasChapters());
|
for (int i = 0; i < queue.size(); i++) {
|
||||||
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
assertTrue(savedQueue.get(i).getId() != 0);
|
||||||
assertTrue(item.hasChapters());
|
assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
|
||||||
assertNotNull(item.getChapters());
|
|
||||||
assertEquals(numChapters, item.getChapters().size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private List<FeedItem> saveDownloadedItems(int numItems) {
|
||||||
|
if (numItems <= 0) {
|
||||||
|
throw new IllegalArgumentException("numItems<=0");
|
||||||
|
}
|
||||||
|
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
for (Feed f : feeds) {
|
||||||
|
items.addAll(f.getItems());
|
||||||
|
}
|
||||||
|
List<FeedItem> downloaded = new ArrayList<>();
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
while (downloaded.size() < numItems) {
|
||||||
|
int i = random.nextInt(numItems);
|
||||||
|
if (!downloaded.contains(items.get(i))) {
|
||||||
|
FeedItem item = items.get(i);
|
||||||
|
item.getMedia().setDownloaded(true);
|
||||||
|
item.getMedia().setFile_url("file" + i);
|
||||||
|
downloaded.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
adapter.storeFeedItemlist(downloaded);
|
||||||
|
adapter.close();
|
||||||
|
return downloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDownloadedItems() {
|
||||||
|
final int numItems = 10;
|
||||||
|
List<FeedItem> downloaded = saveDownloadedItems(numItems);
|
||||||
|
List<FeedItem> downloadedSaved = DBReader.getDownloadedItems();
|
||||||
|
assertNotNull(downloadedSaved);
|
||||||
|
assertEquals(downloaded.size(), downloadedSaved.size());
|
||||||
|
for (FeedItem item : downloadedSaved) {
|
||||||
|
assertNotNull(item.getMedia());
|
||||||
|
assertTrue(item.getMedia().isDownloaded());
|
||||||
|
assertNotNull(item.getMedia().getDownload_url());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
private List<FeedItem> saveNewItems(int numItems) {
|
||||||
|
List<Feed> feeds = saveFeedlist(numItems, numItems, true);
|
||||||
|
List<FeedItem> items = new ArrayList<>();
|
||||||
|
for (Feed f : feeds) {
|
||||||
|
items.addAll(f.getItems());
|
||||||
|
}
|
||||||
|
List<FeedItem> newItems = new ArrayList<>();
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
while (newItems.size() < numItems) {
|
||||||
|
int i = random.nextInt(numItems);
|
||||||
|
if (!newItems.contains(items.get(i))) {
|
||||||
|
FeedItem item = items.get(i);
|
||||||
|
item.setNew();
|
||||||
|
newItems.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
adapter.storeFeedItemlist(newItems);
|
||||||
|
adapter.close();
|
||||||
|
return newItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNewItemIds() {
|
||||||
|
final int numItems = 10;
|
||||||
|
|
||||||
|
List<FeedItem> newItems = saveNewItems(numItems);
|
||||||
|
long[] unreadIds = new long[newItems.size()];
|
||||||
|
for (int i = 0; i < newItems.size(); i++) {
|
||||||
|
unreadIds[i] = newItems.get(i).getId();
|
||||||
|
}
|
||||||
|
List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
|
||||||
|
assertNotNull(newItemsSaved);
|
||||||
|
assertEquals(newItemsSaved.size(), newItems.size());
|
||||||
|
for (FeedItem feedItem : newItemsSaved) {
|
||||||
|
long savedId = feedItem.getId();
|
||||||
|
boolean found = false;
|
||||||
|
for (long id : unreadIds) {
|
||||||
|
if (id == savedId) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPlaybackHistoryLength() {
|
||||||
|
final int totalItems = 100;
|
||||||
|
|
||||||
|
Feed feed = DbTestUtils.saveFeedlist(1, totalItems, true).get(0);
|
||||||
|
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
for (int playedItems : Arrays.asList(0, 1, 20, 100)) {
|
||||||
|
adapter.open();
|
||||||
|
for (int i = 0; i < playedItems; ++i) {
|
||||||
|
FeedMedia m = feed.getItems().get(i).getMedia();
|
||||||
|
m.setPlaybackCompletionDate(new Date(i + 1));
|
||||||
|
|
||||||
|
adapter.setFeedMediaPlaybackCompletionDate(m);
|
||||||
|
}
|
||||||
|
adapter.close();
|
||||||
|
|
||||||
|
long len = DBReader.getPlaybackHistoryLength();
|
||||||
|
assertEquals("Wrong size: ", (int) len, playedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
|
||||||
|
final int numFeeds = 10;
|
||||||
|
final int numItems = 10;
|
||||||
|
DbTestUtils.saveFeedlist(numFeeds, numItems, true);
|
||||||
|
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
|
||||||
|
assertEquals(numFeeds, navDrawerData.items.size());
|
||||||
|
assertEquals(0, navDrawerData.numNewItems);
|
||||||
|
assertEquals(0, navDrawerData.queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
|
||||||
|
final int numFeeds = 10;
|
||||||
|
final int numItems = 10;
|
||||||
|
final int numQueue = 1;
|
||||||
|
final int numNew = 2;
|
||||||
|
List<Feed> feeds = DbTestUtils.saveFeedlist(numFeeds, numItems, true);
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
for (int i = 0; i < numNew; i++) {
|
||||||
|
FeedItem item = feeds.get(0).getItems().get(i);
|
||||||
|
item.setNew();
|
||||||
|
adapter.setSingleFeedItem(item);
|
||||||
|
}
|
||||||
|
List<FeedItem> queue = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numQueue; i++) {
|
||||||
|
FeedItem item = feeds.get(1).getItems().get(i);
|
||||||
|
queue.add(item);
|
||||||
|
}
|
||||||
|
adapter.setQueue(queue);
|
||||||
|
|
||||||
|
adapter.close();
|
||||||
|
|
||||||
|
NavDrawerData navDrawerData = DBReader.getNavDrawerData();
|
||||||
|
assertEquals(numFeeds, navDrawerData.items.size());
|
||||||
|
assertEquals(numNew, navDrawerData.numNewItems);
|
||||||
|
assertEquals(numQueue, navDrawerData.queueSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFeedItemlistCheckChaptersFalse() {
|
||||||
|
List<Feed> feeds = DbTestUtils.saveFeedlist(10, 10, false, false, 0);
|
||||||
|
for (Feed feed : feeds) {
|
||||||
|
for (FeedItem item : feed.getItems()) {
|
||||||
|
assertFalse(item.hasChapters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFeedItemlistCheckChaptersTrue() {
|
||||||
|
List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
|
||||||
|
for (Feed feed : feeds) {
|
||||||
|
for (FeedItem item : feed.getItems()) {
|
||||||
|
assertTrue(item.hasChapters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadChaptersOfFeedItemNoChapters() {
|
||||||
|
List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
|
||||||
|
saveFeedlist(1, 3, false, true, 3);
|
||||||
|
for (Feed feed : feeds) {
|
||||||
|
for (FeedItem item : feed.getItems()) {
|
||||||
|
assertFalse(item.hasChapters());
|
||||||
|
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
||||||
|
assertFalse(item.hasChapters());
|
||||||
|
assertNull(item.getChapters());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadChaptersOfFeedItemWithChapters() {
|
||||||
|
final int numChapters = 3;
|
||||||
|
DbTestUtils.saveFeedlist(1, 3, false, false, 0);
|
||||||
|
List<Feed> feeds = saveFeedlist(1, 3, false, true, numChapters);
|
||||||
|
for (Feed feed : feeds) {
|
||||||
|
for (FeedItem item : feed.getItems()) {
|
||||||
|
assertTrue(item.hasChapters());
|
||||||
|
item.setChapters(DBReader.loadChaptersOfFeedItem(item));
|
||||||
|
assertTrue(item.hasChapters());
|
||||||
|
assertNotNull(item.getChapters());
|
||||||
|
assertEquals(numChapters, item.getChapters().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetItemWithChapters() {
|
||||||
|
final int numChapters = 3;
|
||||||
|
List<Feed> feeds = saveFeedlist(1, 1, false, true, numChapters);
|
||||||
|
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||||
|
FeedItem item2 = DBReader.getFeedItem(item1.getId());
|
||||||
|
item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
|
||||||
|
assertTrue(item2.hasChapters());
|
||||||
|
assertEquals(item1.getChapters(), item2.getChapters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetItemByEpisodeUrl() {
|
||||||
|
List<Feed> feeds = saveFeedlist(1, 1, true);
|
||||||
|
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||||
|
FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
|
||||||
|
item1.getMedia().getDownload_url());
|
||||||
|
assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetItemByGuid() {
|
||||||
|
List<Feed> feeds = saveFeedlist(1, 1, true);
|
||||||
|
FeedItem item1 = feeds.get(0).getItems().get(0);
|
||||||
|
|
||||||
|
FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
|
||||||
|
item1.getMedia().getDownload_url());
|
||||||
|
assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||||
public void testGetItemWithChapters() {
|
public static class PlaybackHistoryTest extends TestBase {
|
||||||
final int numChapters = 3;
|
|
||||||
List<Feed> feeds = saveFeedlist(1, 1, false, true, numChapters);
|
|
||||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
|
||||||
FeedItem item2 = DBReader.getFeedItem(item1.getId());
|
|
||||||
item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
|
|
||||||
assertTrue(item2.hasChapters());
|
|
||||||
assertEquals(item1.getChapters(), item2.getChapters());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
private int paramOffset;
|
||||||
public void testGetItemByEpisodeUrl() {
|
private int paramLimit;
|
||||||
List<Feed> feeds = saveFeedlist(1, 1, true);
|
|
||||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
|
||||||
FeedItem feedItemByEpisodeUrl = DBReader.getFeedItemByGuidOrEpisodeUrl(null,
|
|
||||||
item1.getMedia().getDownload_url());
|
|
||||||
assertEquals(item1.getItemIdentifier(), feedItemByEpisodeUrl.getItemIdentifier());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@ParameterizedRobolectricTestRunner.Parameters
|
||||||
public void testGetItemByGuid() {
|
public static Collection<Object[]> data() {
|
||||||
List<Feed> feeds = saveFeedlist(1, 1, true);
|
List<Integer> limits = Arrays.asList(1, 20, 100);
|
||||||
FeedItem item1 = feeds.get(0).getItems().get(0);
|
List<Integer> offsets = Arrays.asList(0, 10, 20);
|
||||||
|
Object[][] rv = new Object[limits.size() * offsets.size()][2];
|
||||||
|
int i = 0;
|
||||||
|
for (int offset : offsets) {
|
||||||
|
for (int limit : limits) {
|
||||||
|
rv[i][0] = offset;
|
||||||
|
rv[i][1] = limit;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FeedItem feedItemByGuid = DBReader.getFeedItemByGuidOrEpisodeUrl(item1.getItemIdentifier(),
|
return Arrays.asList(rv);
|
||||||
item1.getMedia().getDownload_url());
|
}
|
||||||
assertEquals(item1.getItemIdentifier(), feedItemByGuid.getItemIdentifier());
|
|
||||||
|
public PlaybackHistoryTest(int offset, int limit) {
|
||||||
|
this.paramOffset = offset;
|
||||||
|
this.paramLimit = limit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetPlaybackHistory() {
|
||||||
|
final int numItems = (paramLimit + 1) * 2;
|
||||||
|
final int playedItems = paramLimit + 1;
|
||||||
|
final int numReturnedItems = Math.min(Math.max(playedItems - paramOffset, 0), paramLimit);
|
||||||
|
final int numFeeds = 1;
|
||||||
|
|
||||||
|
Feed feed = DbTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
|
||||||
|
long[] ids = new long[playedItems];
|
||||||
|
|
||||||
|
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||||
|
adapter.open();
|
||||||
|
for (int i = 0; i < playedItems; i++) {
|
||||||
|
FeedMedia m = feed.getItems().get(i).getMedia();
|
||||||
|
m.setPlaybackCompletionDate(new Date(i + 1));
|
||||||
|
adapter.setFeedMediaPlaybackCompletionDate(m);
|
||||||
|
ids[ids.length - 1 - i] = m.getItem().getId();
|
||||||
|
}
|
||||||
|
adapter.close();
|
||||||
|
|
||||||
|
List<FeedItem> saved = DBReader.getPlaybackHistory(paramOffset, paramLimit);
|
||||||
|
assertNotNull(saved);
|
||||||
|
assertEquals(String.format("Wrong size with offset %d and limit %d: ",
|
||||||
|
paramOffset, paramLimit),
|
||||||
|
numReturnedItems, saved.size());
|
||||||
|
for (int i = 0; i < numReturnedItems; i++) {
|
||||||
|
FeedItem item = saved.get(i);
|
||||||
|
assertNotNull(item.getMedia().getPlaybackCompletionDate());
|
||||||
|
assertEquals(String.format("Wrong sort order with offset %d and limit %d: ",
|
||||||
|
paramOffset, paramLimit),
|
||||||
|
item.getId(), ids[paramOffset + i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1076,18 +1076,23 @@ public class PodDBAdapter {
|
||||||
* Returns a cursor which contains feed media objects with a playback
|
* Returns a cursor which contains feed media objects with a playback
|
||||||
* completion date in ascending order.
|
* completion date in ascending order.
|
||||||
*
|
*
|
||||||
|
* @param offset The row to start at.
|
||||||
* @param limit The maximum row count of the returned cursor. Must be an
|
* @param limit The maximum row count of the returned cursor. Must be an
|
||||||
* integer >= 0.
|
* integer >= 0.
|
||||||
* @throws IllegalArgumentException if limit < 0
|
* @throws IllegalArgumentException if limit < 0
|
||||||
*/
|
*/
|
||||||
public final Cursor getCompletedMediaCursor(int limit) {
|
public final Cursor getCompletedMediaCursor(int offset, int limit) {
|
||||||
if (limit < 0) {
|
if (limit < 0) {
|
||||||
throw new IllegalArgumentException("Limit must be >= 0");
|
throw new IllegalArgumentException("Limit must be >= 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.query(TABLE_NAME_FEED_MEDIA, null,
|
return db.query(TABLE_NAME_FEED_MEDIA, null,
|
||||||
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
|
KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
|
||||||
null, String.format(Locale.US, "%s DESC LIMIT %d", KEY_PLAYBACK_COMPLETION_DATE, limit));
|
null, String.format(Locale.US, "%s DESC LIMIT %d, %d", KEY_PLAYBACK_COMPLETION_DATE, offset, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final long getCompletedMediaLength() {
|
||||||
|
return DatabaseUtils.queryNumEntries(db, TABLE_NAME_FEED_MEDIA, KEY_PLAYBACK_COMPLETION_DATE + "> 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Cursor getSingleFeedMediaCursor(long id) {
|
public final Cursor getSingleFeedMediaCursor(long id) {
|
||||||
|
|
Loading…
Reference in New Issue