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.
|
||||
* 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 remotePlaylists remote playlists in the display index order
|
||||
* @param localPlaylists local playlists
|
||||
* @param remotePlaylists remote playlists
|
||||
* @return merged playlists
|
||||
*/
|
||||
static List<PlaylistLocalItem> merge(
|
||||
final List<PlaylistMetadataEntry> localPlaylists,
|
||||
final List<PlaylistRemoteEntity> remotePlaylists) {
|
||||
|
||||
for (int i = 1; i < localPlaylists.size(); i++) {
|
||||
if (localPlaylists.get(i).getDisplayIndex()
|
||||
< localPlaylists.get(i - 1).getDisplayIndex()) {
|
||||
throw new IllegalArgumentException(
|
||||
"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");
|
||||
}
|
||||
}
|
||||
Collections.sort(localPlaylists,
|
||||
Comparator.comparingLong(PlaylistMetadataEntry::getDisplayIndex));
|
||||
Collections.sort(remotePlaylists,
|
||||
Comparator.comparingLong(PlaylistRemoteEntity::getDisplayIndex));
|
||||
|
||||
// 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 java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import icepick.State;
|
||||
|
@ -70,8 +68,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
|
||||
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;
|
||||
private List<Pair<Long, LocalItem.LocalItemType>> deletedItems;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Fragment LifeCycle - Creation
|
||||
|
@ -89,9 +86,9 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
disposables = new CompositeDisposable();
|
||||
|
||||
isLoadingComplete = new AtomicBoolean();
|
||||
debounceSaver = new DebounceSaver(this);
|
||||
debounceSaver = new DebounceSaver(3000, this);
|
||||
|
||||
displayIndexInDatabase = new HashMap<>();
|
||||
deletedItems = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -186,7 +183,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
isLoadingComplete.set(false);
|
||||
|
||||
Flowable.combineLatest(localPlaylistManager.getDisplayIndexOrderedPlaylists(),
|
||||
remotePlaylistManager.getDisplayIndexOrderedPlaylists(), PlaylistLocalItem::merge)
|
||||
remotePlaylistManager.getDisplayIndexOrderedPlaylists(),
|
||||
PlaylistLocalItem::merge)
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(getPlaylistsSubscriber());
|
||||
|
@ -237,7 +235,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
itemsListState = null;
|
||||
|
||||
isLoadingComplete = null;
|
||||
displayIndexInDatabase = null;
|
||||
deletedItems = null;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
@ -343,7 +341,15 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
}
|
||||
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) {
|
||||
|
@ -351,9 +357,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
return;
|
||||
}
|
||||
|
||||
displayIndexInDatabase.clear();
|
||||
|
||||
// If the display index does not match actual index in the list, update the display index.
|
||||
// Check if the display index does not match the actual index in the list.
|
||||
// This may happen when a new list is created
|
||||
// or on the first run after database migration
|
||||
// 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);
|
||||
if (item.getDisplayIndex() != i) {
|
||||
isDisplayIndexModified = true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (item instanceof PlaylistMetadataEntry) {
|
||||
if (((PlaylistMetadataEntry) item).getDisplayIndex() != 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);
|
||||
}
|
||||
} else if (item instanceof PlaylistRemoteEntity) {
|
||||
if (((PlaylistRemoteEntity) item).getDisplayIndex() != 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find deleted items
|
||||
for (final Pair<Long, LocalItem.LocalItemType> key : displayIndexInDatabase.keySet()) {
|
||||
if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
|
||||
localItemsDeleteUid.add(key.first);
|
||||
} else if (key.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
|
||||
remoteItemsDeleteUid.add(key.first);
|
||||
for (final Pair<Long, LocalItem.LocalItemType> item : deletedItems) {
|
||||
if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_LOCAL_ITEM)) {
|
||||
localItemsDeleteUid.add(item.first);
|
||||
} else if (item.second.equals(LocalItem.LocalItemType.PLAYLIST_REMOTE_ITEM)) {
|
||||
remoteItemsDeleteUid.add(item.first);
|
||||
}
|
||||
}
|
||||
|
||||
displayIndexInDatabase.clear();
|
||||
deletedItems.clear();
|
||||
|
||||
// 1. Update local playlists
|
||||
// 2. Update remote playlists
|
||||
|
@ -515,7 +487,7 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
if (isSwapped) {
|
||||
debounceSaver.saveChanges();
|
||||
debounceSaver.setHasChangesToSave();
|
||||
}
|
||||
return isSwapped;
|
||||
}
|
||||
|
|
|
@ -441,7 +441,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
|
||||
itemListAdapter.clearStreamItemList();
|
||||
itemListAdapter.addItems(notWatchedItems);
|
||||
debounceSaver.saveChanges();
|
||||
debounceSaver.setHasChangesToSave();
|
||||
|
||||
|
||||
if (thumbnailVideoRemoved) {
|
||||
|
@ -609,7 +609,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
}
|
||||
|
||||
setVideoCount(itemListAdapter.getItemsList().size());
|
||||
debounceSaver.saveChanges();
|
||||
debounceSaver.setHasChangesToSave();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -687,7 +687,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
|||
final int targetIndex = target.getBindingAdapterPosition();
|
||||
final boolean isSwapped = itemListAdapter.swapItems(sourceIndex, targetIndex);
|
||||
if (isSwapped) {
|
||||
debounceSaver.saveChanges();
|
||||
debounceSaver.setHasChangesToSave();
|
||||
}
|
||||
return isSwapped;
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ public class DebounceSaver {
|
|||
UserAction.SOMETHING_ELSE, "Debounced saver")));
|
||||
}
|
||||
|
||||
public void saveChanges() {
|
||||
public void setHasChangesToSave() {
|
||||
if (isModified == null || debouncedSaveSignal == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -36,16 +36,6 @@ public class PlaylistLocalItemTest {
|
|||
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
|
||||
public void onlyRemotePlaylists() {
|
||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
||||
|
@ -65,19 +55,6 @@ public class PlaylistLocalItemTest {
|
|||
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
|
||||
public void sameIndexWithDifferentName() {
|
||||
final List<PlaylistMetadataEntry> localPlaylists = new ArrayList<>();
|
||||
|
|
Loading…
Reference in a new issue