Update index modification logic & redo sorting in the merge algorithm
This commit is contained in:
parent
4e401bc059
commit
898a936064
5 changed files with 39 additions and 101 deletions
|
@ -21,29 +21,18 @@ public interface PlaylistLocalItem extends LocalItem {
|
||||||
* Merge localPlaylists and remotePlaylists by the display index.
|
* Merge localPlaylists and remotePlaylists by the display index.
|
||||||
* If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
|
* If two items have the same display index, sort them in {@code CASE_INSENSITIVE_ORDER}.
|
||||||
*
|
*
|
||||||
* @param localPlaylists local playlists in the display index order
|
* @param localPlaylists local playlists
|
||||||
* @param remotePlaylists remote playlists in the display index order
|
* @param remotePlaylists remote playlists
|
||||||
* @return merged playlists
|
* @return merged playlists
|
||||||
*/
|
*/
|
||||||
static List<PlaylistLocalItem> merge(
|
static List<PlaylistLocalItem> merge(
|
||||||
final List<PlaylistMetadataEntry> localPlaylists,
|
final List<PlaylistMetadataEntry> localPlaylists,
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||||
|
|
||||||
for (int i = 1; i < localPlaylists.size(); i++) {
|
Collections.sort(localPlaylists,
|
||||||
if (localPlaylists.get(i).getDisplayIndex()
|
Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
|
||||||
< localPlaylists.get(i - 1).getDisplayIndex()) {
|
Collections.sort(remotePlaylists,
|
||||||
throw new IllegalArgumentException(
|
Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
|
||||||
"localPlaylists is not in the display index order");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 1; i < remotePlaylists.size(); i++) {
|
|
||||||
if (remotePlaylists.get(i).getDisplayIndex()
|
|
||||||
< remotePlaylists.get(i - 1).getDisplayIndex()) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"remotePlaylists is not in the display index order");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This algorithm is similar to the merge operation in merge sort.
|
// This algorithm is similar to the merge operation in merge sort.
|
||||||
|
|
||||||
|
|
|
@ -41,9 +41,7 @@ import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.OnClickGesture;
|
import org.schabi.newpipe.util.OnClickGesture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
|
@ -70,8 +68,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
|
|
||||||
private DebounceSaver debounceSaver;
|
private DebounceSaver debounceSaver;
|
||||||
|
|
||||||
// Map from (uid, local/remote item) to the saved display index in the database.
|
private List<Pair<Long, LocalItem.LocalItemType>> deletedItems;
|
||||||
private Map<Pair<Long, LocalItem.LocalItemType>, Long> displayIndexInDatabase;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Fragment LifeCycle - Creation
|
// Fragment LifeCycle - Creation
|
||||||
|
@ -89,9 +86,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
disposables = new CompositeDisposable();
|
disposables = new CompositeDisposable();
|
||||||
|
|
||||||
isLoadingComplete = new AtomicBoolean();
|
isLoadingComplete = new AtomicBoolean();
|
||||||
debounceSaver = new DebounceSaver(this);
|
debounceSaver = new DebounceSaver(3000, this);
|
||||||
|
|
||||||
displayIndexInDatabase = new HashMap<>();
|
deletedItems = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -186,7 +183,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
isLoadingComplete.set(false);
|
isLoadingComplete.set(false);
|
||||||
|
|
||||||
Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
|
Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
|
||||||
remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
|
remotePlaylistManager.getDisplayIndexOrderedPlaylists(),
|
||||||
|
PlaylistLocalItem::merge)
|
||||||
.onBackpressureLatest()
|
.onBackpressureLatest()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(getPlaylistsSubscriber());
|
.subscribe(getPlaylistsSubscriber());
|
||||||
|
@ -237,7 +235,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
itemsListState = null;
|
itemsListState = null;
|
||||||
|
|
||||||
isLoadingComplete = null;
|
isLoadingComplete = null;
|
||||||
displayIndexInDatabase = null;
|
deletedItems = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -343,7 +341,15 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
}
|
}
|
||||||
itemListAdapter.removeItem(item);
|
itemListAdapter.removeItem(item);
|
||||||
|
|
||||||
debounceSaver.saveChanges();
|
if (item instanceof PlaylistMetadataEntry) {
|
||||||
|
deletedItems.add(new Pair<>(item.getUid(),
|
||||||
|
LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM));
|
||||||
|
} else if (item instanceof PlaylistRemoteEntity) {
|
||||||
|
deletedItems.add(new Pair<>(item.getUid(),
|
||||||
|
LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM));
|
||||||
|
}
|
||||||
|
|
||||||
|
debounceSaver.setHasChangesToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
|
private void checkDisplayIndexModified(@NonNull final List<PlaylistLocalItem> result) {
|
||||||
|
@ -351,9 +357,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayIndexInDatabase.clear();
|
// Check if the display index does not match the actual index in the list.
|
||||||
|
|
||||||
// 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
|
// This may happen when a new list is created
|
||||||
// or on the first run after database migration
|
// or on the first run after database migration
|
||||||
// or display index is not continuous for some reason
|
// or display index is not continuous for some reason
|
||||||
|
@ -363,29 +367,12 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
final PlaylistLocalItem item = result.get(i);
|
final PlaylistLocalItem item = result.get(i);
|
||||||
if (item.getDisplayIndex() != i) {
|
if (item.getDisplayIndex() != i) {
|
||||||
isDisplayIndexModified = true;
|
isDisplayIndexModified = true;
|
||||||
}
|
break;
|
||||||
|
|
||||||
// Updating display index in the item does not affect the value inserts into
|
|
||||||
// database, which will be recalculated during the database update. Updating
|
|
||||||
// display index in the item here is to determine whether it is recently modified.
|
|
||||||
// Save the index read from the database.
|
|
||||||
if (item instanceof PlaylistMetadataEntry) {
|
|
||||||
|
|
||||||
displayIndexInDatabase.put(new Pair<>(item.getUid(),
|
|
||||||
LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM), item.getDisplayIndex());
|
|
||||||
item.setDisplayIndex(i);
|
|
||||||
|
|
||||||
} else if (item instanceof PlaylistRemoteEntity) {
|
|
||||||
|
|
||||||
displayIndexInDatabase.put(new Pair<>(item.getUid(),
|
|
||||||
LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM), item.getDisplayIndex());
|
|
||||||
item.setDisplayIndex(i);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debounceSaver != null && isDisplayIndexModified) {
|
if (debounceSaver != null && isDisplayIndexModified) {
|
||||||
debounceSaver.saveChanges();
|
debounceSaver.setHasChangesToSave();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,43 +401,28 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
final LocalItem item = items.get(i);
|
final LocalItem item = items.get(i);
|
||||||
|
|
||||||
if (item instanceof PlaylistMetadataEntry) {
|
if (item instanceof PlaylistMetadataEntry) {
|
||||||
|
if (((PlaylistMetadataEntry) item).getDisplayIndex() != i) {
|
||||||
((PlaylistMetadataEntry) item).setDisplayIndex(i);
|
((PlaylistMetadataEntry) item).setDisplayIndex(i);
|
||||||
|
|
||||||
final Long uid = ((PlaylistMetadataEntry) item).getUid();
|
|
||||||
final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
|
|
||||||
LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM);
|
|
||||||
final Long databaseIndex = displayIndexInDatabase.remove(key);
|
|
||||||
|
|
||||||
// The database index should not be null because inserting new item into database
|
|
||||||
// is not handled here. NullPointerException has occurred once, but I can't
|
|
||||||
// reproduce it. Enhance robustness here.
|
|
||||||
if (databaseIndex != null && databaseIndex != i) {
|
|
||||||
localItemsUpdate.add((PlaylistMetadataEntry) item);
|
localItemsUpdate.add((PlaylistMetadataEntry) item);
|
||||||
}
|
}
|
||||||
} else if (item instanceof PlaylistRemoteEntity) {
|
} else if (item instanceof PlaylistRemoteEntity) {
|
||||||
|
if (((PlaylistRemoteEntity) item).getDisplayIndex() != i) {
|
||||||
((PlaylistRemoteEntity) item).setDisplayIndex(i);
|
((PlaylistRemoteEntity) item).setDisplayIndex(i);
|
||||||
|
|
||||||
final Long uid = ((PlaylistRemoteEntity) item).getUid();
|
|
||||||
final Pair<Long, LocalItem.LocalItemType> key = new Pair<>(uid,
|
|
||||||
LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM);
|
|
||||||
final Long databaseIndex = displayIndexInDatabase.remove(key);
|
|
||||||
|
|
||||||
if (databaseIndex != null && databaseIndex != i) {
|
|
||||||
remoteItemsUpdate.add((PlaylistRemoteEntity) item);
|
remoteItemsUpdate.add((PlaylistRemoteEntity) item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find deleted items
|
// Find deleted items
|
||||||
for (final Pair<Long, LocalItem.LocalItemType> key : displayIndexInDatabase.keySet()) {
|
for (final Pair<Long, LocalItem.LocalItemType> item : deletedItems) {
|
||||||
if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
|
if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
|
||||||
localItemsDeleteUid.add(key.first);
|
localItemsDeleteUid.add(item.first);
|
||||||
} else if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
|
} else if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
|
||||||
remoteItemsDeleteUid.add(key.first);
|
remoteItemsDeleteUid.add(item.first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayIndexInDatabase.clear();
|
deletedItems.clear();
|
||||||
|
|
||||||
// 1. Update local playlists
|
// 1. Update local playlists
|
||||||
// 2. Update remote playlists
|
// 2. Update remote playlists
|
||||||
|
@ -515,7 +487,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
||||||
final int targetIndex = target.getBindingAdapterPosition();
|
final int targetIndex = target.getBindingAdapterPosition();
|
||||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||||
if (isSwapped) {
|
if (isSwapped) {
|
||||||
debounceSaver.saveChanges();
|
debounceSaver.setHasChangesToSave();
|
||||||
}
|
}
|
||||||
return isSwapped;
|
return isSwapped;
|
||||||
}
|
}
|
||||||
|
|
|
@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
|
|
||||||
itemListAdapter.clearStreamItemList();
|
itemListAdapter.clearStreamItemList();
|
||||||
itemListAdapter.addItems(notWatchedItems);
|
itemListAdapter.addItems(notWatchedItems);
|
||||||
debounceSaver.saveChanges();
|
debounceSaver.setHasChangesToSave();
|
||||||
|
|
||||||
|
|
||||||
if (thumbnailVideoRemoved) {
|
if (thumbnailVideoRemoved) {
|
||||||
|
@ -609,7 +609,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideoCount(itemListAdapter.getItemsList().size());
|
setVideoCount(itemListAdapter.getItemsList().size());
|
||||||
debounceSaver.saveChanges();
|
debounceSaver.setHasChangesToSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -687,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||||
final int targetIndex = target.getBindingAdapterPosition();
|
final int targetIndex = target.getBindingAdapterPosition();
|
||||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||||
if (isSwapped) {
|
if (isSwapped) {
|
||||||
debounceSaver.saveChanges();
|
debounceSaver.setHasChangesToSave();
|
||||||
}
|
}
|
||||||
return isSwapped;
|
return isSwapped;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class DebounceSaver {
|
||||||
UserAction.SOMETHING_ELSE, "Debounced saver")));
|
UserAction.SOMETHING_ELSE, "Debounced saver")));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveChanges() {
|
public void setHasChangesToSave() {
|
||||||
if (isModified == null || debouncedSaveSignal == null) {
|
if (isModified == null || debouncedSaveSignal == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,16 +36,6 @@ public class PlaylistLocalItemTest {
|
||||||
assertEquals(3, mergedPlaylists.get(2).getDisplayIndex());
|
assertEquals(3, mergedPlaylists.get(2).getDisplayIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void invalidLocalPlaylists() {
|
|
||||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
|
|
||||||
localPlaylists.add(new PlaylistMetadataEntry(1, "name1", "", 2, 1));
|
|
||||||
localPlaylists.add(new PlaylistMetadataEntry(2, "name2", "", 1, 1));
|
|
||||||
localPlaylists.add(new PlaylistMetadataEntry(3, "name3", "", 0, 1));
|
|
||||||
PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onlyRemotePlaylists() {
|
public void onlyRemotePlaylists() {
|
||||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
||||||
|
@ -65,19 +55,6 @@ public class PlaylistLocalItemTest {
|
||||||
assertEquals(4, mergedPlaylists.get(2).getDisplayIndex());
|
assertEquals(4, mergedPlaylists.get(2).getDisplayIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void invalidRemotePlaylists() {
|
|
||||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
|
||||||
final List<PlaylistRemoteEntity> remotePlaylists = new ArrayList<>();
|
|
||||||
remotePlaylists.add(new PlaylistRemoteEntity(
|
|
||||||
1, "name1", "url1", "", "", 1, 1L));
|
|
||||||
remotePlaylists.add(new PlaylistRemoteEntity(
|
|
||||||
2, "name2", "url2", "", "", 3, 1L));
|
|
||||||
remotePlaylists.add(new PlaylistRemoteEntity(
|
|
||||||
3, "name3", "url3", "", "", 0, 1L));
|
|
||||||
PlaylistLocalItem.merge(localPlaylists, remotePlaylists);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sameIndexWithDifferentName() {
|
public void sameIndexWithDifferentName() {
|
||||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
||||||
|
|
Loading…
Reference in a new issue