Update index modification logic & redo sorting in the merge algorithm

This commit is contained in:
GGAutomaton 2022-06-23 23:19:59 +08:00
parent 4e401bc059
commit 898a936064
5 changed files with 39 additions and 101 deletions

View file

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

View file

@ -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) {
((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) {
if (((PlaylistMetadataEntry) item).getDisplayIndex() != i) {
((PlaylistMetadataEntry) item).setDisplayIndex(i);
localItemsUpdate.add((PlaylistMetadataEntry) item);
}
} else if (item instanceof PlaylistRemoteEntity) {
((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) {
if (((PlaylistRemoteEntity) item).getDisplayIndex() != i) {
((PlaylistRemoteEntity) item).setDisplayIndex(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;
}

View file

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

View file

@ -70,7 +70,7 @@ public class DebounceSaver {
UserAction.SOMETHING_ELSE, "Debounced saver")));
}
public void saveChanges() {
public void setHasChangesToSave() {
if (isModified == null || debouncedSaveSignal == null) {
return;
}

View file

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