Reuse DebounceSaver
This commit is contained in:
parent
bd1aae8d66
commit
bb5390d63a
5 changed files with 142 additions and 103 deletions
|
@ -17,15 +17,15 @@ public interface PlaylistLocalItem extends LocalItem {
|
|||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
|
||||
// Merge localPlaylists and remotePlaylists by displayIndex.
|
||||
// If two items have the same displayIndex, sort them in CASE_INSENSITIVE_ORDER.
|
||||
// Merge localPlaylists and remotePlaylists by display index.
|
||||
// If two items have the same display index, sort them in CASE_INSENSITIVE_ORDER.
|
||||
// This algorithm is similar to the merge operation in merge sort.
|
||||
|
||||
final List<PlaylistLocalItem> result = new ArrayList<>(
|
||||
localPlaylists.size() + remotePlaylists.size());
|
||||
final List<PlaylistLocalItem> itemsWithSameIndex = new ArrayList<>();
|
||||
|
||||
// The data from database may not be in the displayIndex order
|
||||
// The data from database may not be in the display index order
|
||||
Collections.sort(localPlaylists,
|
||||
Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
|
||||
Collections.sort(remotePlaylists,
|
||||
|
@ -58,7 +58,7 @@ public interface PlaylistLocalItem extends LocalItem {
|
|||
final List<PlaylistLocalItem> itemsWithSameIndex) {
|
||||
if (!itemsWithSameIndex.isEmpty()
|
||||
&& itemsWithSameIndex.get(0).getDisplayIndex() != item.getDisplayIndex()) {
|
||||
// The new item has a different displayIndex, add previous items with same
|
||||
// The new item has a different display index, add previous items with same
|
||||
// index to the result.
|
||||
addItemsWithSameIndex(result, itemsWithSameIndex);
|
||||
itemsWithSameIndex.clear();
|
||||
|
|
|
@ -35,6 +35,8 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
|
|||
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.util.DebounceSavable;
|
||||
import org.schabi.newpipe.util.DebounceSaver;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
||||
|
@ -42,7 +44,6 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import icepick.State;
|
||||
|
@ -50,12 +51,10 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void> {
|
||||
public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistLocalItem>, Void>
|
||||
implements DebounceSavable {
|
||||
|
||||
// Save the list 10 seconds after the last change occurred
|
||||
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||
@State
|
||||
protected Parcelable itemsListState;
|
||||
|
@ -66,12 +65,10 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
private RemotePlaylistManager remotePlaylistManager;
|
||||
private ItemTouchHelper itemTouchHelper;
|
||||
|
||||
private PublishSubject<Long> debouncedSaveSignal;
|
||||
|
||||
/* Has the playlist been fully loaded from db */
|
||||
/* Have the bookmarked playlists been fully loaded from db */
|
||||
private AtomicBoolean isLoadingComplete;
|
||||
/* Has the playlist been modified (e.g. items reordered or deleted) */
|
||||
private AtomicBoolean isModified;
|
||||
|
||||
private DebounceSaver debounceSaver;
|
||||
|
||||
// Map from (uid, local/remote item) to the saved display index in the database.
|
||||
private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
|
||||
|
@ -91,9 +88,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
remotePlaylistManager = new RemotePlaylistManager(database);
|
||||
disposables = new CompositeDisposable();
|
||||
|
||||
debouncedSaveSignal = PublishSubject.create();
|
||||
isLoadingComplete = new AtomicBoolean();
|
||||
isModified = new AtomicBoolean();
|
||||
debounceSaver = new DebounceSaver(10000, this);
|
||||
|
||||
displayIndexInDatabase = new HashMap<>();
|
||||
}
|
||||
|
@ -183,9 +179,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
public void startLoading(final boolean forceLoad) {
|
||||
super.startLoading(forceLoad);
|
||||
|
||||
disposables.add(getDebouncedSaver());
|
||||
if (debounceSaver != null) {
|
||||
disposables.add(debounceSaver.getDebouncedSaver());
|
||||
debounceSaver.setIsModified(false);
|
||||
}
|
||||
isLoadingComplete.set(false);
|
||||
isModified.set(false);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getPlaylists(),
|
||||
remotePlaylistManager.getPlaylists(), PlaylistLocalItem::merge)
|
||||
|
@ -225,21 +223,20 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (debouncedSaveSignal != null) {
|
||||
debouncedSaveSignal.onComplete();
|
||||
if (debounceSaver != null) {
|
||||
debounceSaver.getDebouncedSaveSignal().onComplete();
|
||||
}
|
||||
if (disposables != null) {
|
||||
disposables.dispose();
|
||||
}
|
||||
|
||||
debouncedSaveSignal = null;
|
||||
debounceSaver = null;
|
||||
disposables = null;
|
||||
localPlaylistManager = null;
|
||||
remotePlaylistManager = null;
|
||||
itemsListState = null;
|
||||
|
||||
isLoadingComplete = null;
|
||||
isModified = null;
|
||||
displayIndexInDatabase = null;
|
||||
}
|
||||
|
||||
|
@ -263,7 +260,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
|
||||
@Override
|
||||
public void onNext(final List<PlaylistLocalItem> subscriptions) {
|
||||
if (isModified == null || !isModified.get()) {
|
||||
if (debounceSaver == null || !debounceSaver.getIsModified()) {
|
||||
checkDisplayIndexModified(subscriptions);
|
||||
handleResult(subscriptions);
|
||||
isLoadingComplete.set(true);
|
||||
|
@ -346,11 +343,11 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
itemListAdapter.removeItem(item);
|
||||
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
}
|
||||
|
||||
private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
|
||||
if (isModified != null && isModified.get()) {
|
||||
if (debounceSaver != null && debounceSaver.getIsModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -358,8 +355,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
|
||||
// If the display index does not match actual index in the list, update the display index.
|
||||
// This may happen when a new list is created
|
||||
// or on the first run after database update
|
||||
// or displayIndex is not continuous for some reason.
|
||||
// or on the first run after database migration
|
||||
// or display index is not continuous for some reason
|
||||
// or the user changes the display index.
|
||||
boolean isDisplayIndexModified = false;
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
final PlaylistLocalItem item = result.get(i);
|
||||
|
@ -388,40 +386,19 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
|
||||
if (isDisplayIndexModified) {
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
if (isModified == null || debouncedSaveSignal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
isModified.set(true);
|
||||
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private Disposable getDebouncedSaver() {
|
||||
if (debouncedSaveSignal == null) {
|
||||
return Disposable.empty();
|
||||
}
|
||||
|
||||
return debouncedSaveSignal
|
||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> saveImmediate(), throwable ->
|
||||
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
||||
"Debounced saver")));
|
||||
}
|
||||
|
||||
private void saveImmediate() {
|
||||
@Override
|
||||
public void saveImmediate() {
|
||||
if (itemListAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// List must be loaded and modified in order to save
|
||||
if (isLoadingComplete == null || isModified == null
|
||||
|| !isLoadingComplete.get() || !isModified.get()) {
|
||||
if (isLoadingComplete == null || debounceSaver == null
|
||||
|| !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
|
||||
Log.w(TAG, "Attempting to save playlists in bookmark when bookmark "
|
||||
+ "is not loaded or playlists not modified");
|
||||
return;
|
||||
|
@ -485,8 +462,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
remoteItemsUpdate, remoteItemsDeleteUid)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
if (isModified != null) {
|
||||
isModified.set(false);
|
||||
if (debounceSaver != null) {
|
||||
debounceSaver.setIsModified(false);
|
||||
}
|
||||
},
|
||||
throwable -> showError(new ErrorInfo(throwable,
|
||||
|
@ -544,7 +521,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
if (isSwapped) {
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
}
|
||||
return isSwapped;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ import org.schabi.newpipe.local.history.HistoryRecordManager;
|
|||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
|
||||
import org.schabi.newpipe.util.DebounceSavable;
|
||||
import org.schabi.newpipe.util.DebounceSaver;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
|
@ -55,7 +57,6 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import icepick.State;
|
||||
|
@ -64,11 +65,9 @@ import io.reactivex.rxjava3.core.Flowable;
|
|||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void> {
|
||||
// Save the list 10 seconds after the last change occurred
|
||||
private static final long SAVE_DEBOUNCE_MILLIS = 10000;
|
||||
public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistStreamEntry>, Void>
|
||||
implements DebounceSavable {
|
||||
private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 12;
|
||||
@State
|
||||
protected Long playlistId;
|
||||
|
@ -85,13 +84,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
private LocalPlaylistManager playlistManager;
|
||||
private Subscription databaseSubscription;
|
||||
|
||||
private PublishSubject<Long> debouncedSaveSignal;
|
||||
private CompositeDisposable disposables;
|
||||
|
||||
/* Has the playlist been fully loaded from db */
|
||||
private AtomicBoolean isLoadingComplete;
|
||||
/* Has the playlist been modified (e.g. items reordered or deleted) */
|
||||
private AtomicBoolean isModified;
|
||||
|
||||
private DebounceSaver debounceSaver;
|
||||
|
||||
/* Is the playlist currently being processed to remove watched videos */
|
||||
private boolean isRemovingWatched = false;
|
||||
|
||||
|
@ -109,12 +108,11 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
playlistManager = new LocalPlaylistManager(NewPipeDatabase.getInstance(requireContext()));
|
||||
debouncedSaveSignal = PublishSubject.create();
|
||||
|
||||
disposables = new CompositeDisposable();
|
||||
|
||||
isLoadingComplete = new AtomicBoolean();
|
||||
isModified = new AtomicBoolean();
|
||||
debounceSaver = new DebounceSaver(10000, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -220,10 +218,13 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
if (disposables != null) {
|
||||
disposables.clear();
|
||||
}
|
||||
disposables.add(getDebouncedSaver());
|
||||
|
||||
if (debounceSaver != null) {
|
||||
disposables.add(debounceSaver.getDebouncedSaver());
|
||||
debounceSaver.setIsModified(false);
|
||||
}
|
||||
|
||||
isLoadingComplete.set(false);
|
||||
isModified.set(false);
|
||||
|
||||
playlistManager.getPlaylistStreams(playlistId)
|
||||
.onBackpressureLatest()
|
||||
|
@ -285,19 +286,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (debouncedSaveSignal != null) {
|
||||
debouncedSaveSignal.onComplete();
|
||||
if (debounceSaver != null) {
|
||||
debounceSaver.getDebouncedSaveSignal().onComplete();
|
||||
}
|
||||
if (disposables != null) {
|
||||
disposables.dispose();
|
||||
}
|
||||
|
||||
debouncedSaveSignal = null;
|
||||
debounceSaver = null;
|
||||
playlistManager = null;
|
||||
disposables = null;
|
||||
|
||||
isLoadingComplete = null;
|
||||
isModified = null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -321,7 +321,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
@Override
|
||||
public void onNext(final List<PlaylistStreamEntry> streams) {
|
||||
// Skip handling the result after it has been modified
|
||||
if (isModified == null || !isModified.get()) {
|
||||
if (debounceSaver == null || !debounceSaver.getIsModified()) {
|
||||
handleResult(streams);
|
||||
isLoadingComplete.set(true);
|
||||
}
|
||||
|
@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
|
||||
itemListAdapter.clearStreamItemList();
|
||||
itemListAdapter.addItems(notWatchedItems);
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
|
||||
|
||||
if (thumbnailVideoRemoved) {
|
||||
|
@ -609,39 +609,18 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
}
|
||||
|
||||
private void saveChanges() {
|
||||
if (isModified == null || debouncedSaveSignal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
isModified.set(true);
|
||||
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private Disposable getDebouncedSaver() {
|
||||
if (debouncedSaveSignal == null) {
|
||||
return Disposable.empty();
|
||||
}
|
||||
|
||||
return debouncedSaveSignal
|
||||
.debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> saveImmediate(), throwable ->
|
||||
showError(new ErrorInfo(throwable, UserAction.SOMETHING_ELSE,
|
||||
"Debounced saver")));
|
||||
}
|
||||
|
||||
private void saveImmediate() {
|
||||
@Override
|
||||
public void saveImmediate() {
|
||||
if (playlistManager == null || itemListAdapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// List must be loaded and modified in order to save
|
||||
if (isLoadingComplete == null || isModified == null
|
||||
|| !isLoadingComplete.get() || !isModified.get()) {
|
||||
if (isLoadingComplete == null || debounceSaver == null
|
||||
|| !isLoadingComplete.get() || !debounceSaver.getIsModified()) {
|
||||
Log.w(TAG, "Attempting to save playlist when local playlist "
|
||||
+ "is not loaded or not modified: playlist id=[" + playlistId + "]");
|
||||
return;
|
||||
|
@ -664,8 +643,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
() -> {
|
||||
if (isModified != null) {
|
||||
isModified.set(false);
|
||||
if (debounceSaver != null) {
|
||||
debounceSaver.setIsModified(false);
|
||||
}
|
||||
},
|
||||
throwable -> showError(new ErrorInfo(throwable,
|
||||
|
@ -708,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
if (isSwapped) {
|
||||
saveChanges();
|
||||
debounceSaver.saveChanges();
|
||||
}
|
||||
return isSwapped;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
|
||||
public interface DebounceSavable {
|
||||
|
||||
/**
|
||||
* Execute operations to save the data. <br>
|
||||
* Must set {@link DebounceSaver#setIsModified(boolean)} false in this method manually
|
||||
* after the data has been saved.
|
||||
*/
|
||||
void saveImmediate();
|
||||
|
||||
void showError(ErrorInfo errorInfo);
|
||||
}
|
68
app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
Normal file
68
app/src/main/java/org/schabi/newpipe/util/DebounceSaver.java
Normal file
|
@ -0,0 +1,68 @@
|
|||
package org.schabi.newpipe.util;
|
||||
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject;
|
||||
|
||||
public class DebounceSaver {
|
||||
|
||||
private final long saveDebounceMillis;
|
||||
|
||||
private final PublishSubject<Long> debouncedSaveSignal;
|
||||
|
||||
private final DebounceSavable debounceSavable;
|
||||
|
||||
// Has the object been modified
|
||||
private final AtomicBoolean isModified;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@code DebounceSaver}.
|
||||
*
|
||||
* @param saveDebounceMillis Save the object milliseconds later after the last change
|
||||
* occurred.
|
||||
* @param debounceSavable The object containing data to be saved.
|
||||
*/
|
||||
public DebounceSaver(final long saveDebounceMillis, final DebounceSavable debounceSavable) {
|
||||
this.saveDebounceMillis = saveDebounceMillis;
|
||||
debouncedSaveSignal = PublishSubject.create();
|
||||
this.debounceSavable = debounceSavable;
|
||||
this.isModified = new AtomicBoolean();
|
||||
}
|
||||
|
||||
public boolean getIsModified() {
|
||||
return isModified.get();
|
||||
}
|
||||
|
||||
public void setIsModified(final boolean isModified) {
|
||||
this.isModified.set(isModified);
|
||||
}
|
||||
|
||||
public PublishSubject<Long> getDebouncedSaveSignal() {
|
||||
return debouncedSaveSignal;
|
||||
}
|
||||
|
||||
public Disposable getDebouncedSaver() {
|
||||
return debouncedSaveSignal
|
||||
.debounce(saveDebounceMillis, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(ignored -> debounceSavable.saveImmediate(), throwable ->
|
||||
debounceSavable.showError(new ErrorInfo(throwable,
|
||||
UserAction.SOMETHING_ELSE, "Debounced saver")));
|
||||
}
|
||||
|
||||
public void saveChanges() {
|
||||
if (isModified == null || debouncedSaveSignal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
isModified.set(true);
|
||||
debouncedSaveSignal.onNext(System.currentTimeMillis());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue