Merge pull request #9707 from Jared234/1473_remove_duplicates_from_playlist

Remove duplicates from playlist feature
This commit is contained in:
Stypox 2023-02-28 22:14:01 +01:00 committed by GitHub
commit 23a20712da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 78 additions and 6 deletions

View file

@ -108,6 +108,23 @@ public interface PlaylistStreamDAO extends BasicDAO<PlaylistStreamEntity> {
+ " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC") + " ORDER BY " + PLAYLIST_NAME + " COLLATE NOCASE ASC")
Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata(); Flowable<List<PlaylistMetadataEntry>> getPlaylistMetadata();
@RewriteQueriesToDropUnusedColumns
@Transaction
@Query("SELECT *, MIN(" + JOIN_INDEX + ")"
+ " FROM " + STREAM_TABLE + " INNER JOIN"
+ " (SELECT " + JOIN_STREAM_ID + "," + JOIN_INDEX
+ " FROM " + PLAYLIST_STREAM_JOIN_TABLE
+ " WHERE " + JOIN_PLAYLIST_ID + " = :playlistId)"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID
+ " LEFT JOIN "
+ "(SELECT " + JOIN_STREAM_ID + " AS " + JOIN_STREAM_ID_ALIAS + ", "
+ STREAM_PROGRESS_MILLIS
+ " FROM " + STREAM_STATE_TABLE + " )"
+ " ON " + STREAM_ID + " = " + JOIN_STREAM_ID_ALIAS
+ " GROUP BY " + STREAM_ID
+ " ORDER BY MIN(" + JOIN_INDEX + ") ASC")
Flowable<List<PlaylistStreamEntry>> getStreamsWithoutDuplicates(long playlistId);
@Transaction @Transaction
@Query("SELECT " + PLAYLIST_TABLE + "." + PLAYLIST_ID + ", " @Query("SELECT " + PLAYLIST_TABLE + "." + PLAYLIST_ID + ", "
+ PLAYLIST_NAME + ", " + PLAYLIST_NAME + ", "

View file

@ -96,8 +96,8 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
private AtomicBoolean isLoadingComplete; private AtomicBoolean isLoadingComplete;
/* Has the playlist been modified (e.g. items reordered or deleted) */ /* Has the playlist been modified (e.g. items reordered or deleted) */
private AtomicBoolean isModified; private AtomicBoolean isModified;
/* Is the playlist currently being processed to remove watched videos */ /* Flag to prevent simultaneous rewrites of the playlist */
private boolean isRemovingWatched = false; private boolean isRewritingPlaylist = false;
public static LocalPlaylistFragment getInstance(final long playlistId, final String name) { public static LocalPlaylistFragment getInstance(final long playlistId, final String name) {
final LocalPlaylistFragment instance = new LocalPlaylistFragment(); final LocalPlaylistFragment instance = new LocalPlaylistFragment();
@ -354,7 +354,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} else if (item.getItemId() == R.id.menu_item_rename_playlist) { } else if (item.getItemId() == R.id.menu_item_rename_playlist) {
createRenameDialog(); createRenameDialog();
} else if (item.getItemId() == R.id.menu_item_remove_watched) { } else if (item.getItemId() == R.id.menu_item_remove_watched) {
if (!isRemovingWatched) { if (!isRewritingPlaylist) {
new AlertDialog.Builder(requireContext()) new AlertDialog.Builder(requireContext())
.setMessage(R.string.remove_watched_popup_warning) .setMessage(R.string.remove_watched_popup_warning)
.setTitle(R.string.remove_watched_popup_title) .setTitle(R.string.remove_watched_popup_title)
@ -368,6 +368,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
.create() .create()
.show(); .show();
} }
} else if (item.getItemId() == R.id.menu_item_remove_duplicates) {
if (!isRewritingPlaylist) {
openRemoveDuplicatesDialog();
}
} else { } else {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -389,10 +393,10 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
public void removeWatchedStreams(final boolean removePartiallyWatched) { public void removeWatchedStreams(final boolean removePartiallyWatched) {
if (isRemovingWatched) { if (isRewritingPlaylist) {
return; return;
} }
isRemovingWatched = true; isRewritingPlaylist = true;
showLoading(); showLoading();
final var recordManager = new HistoryRecordManager(getContext()); final var recordManager = new HistoryRecordManager(getContext());
@ -470,7 +474,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
} }
hideLoading(); hideLoading();
isRemovingWatched = false; isRewritingPlaylist = false;
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK, }, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
"Removing watched videos, partially watched=" + removePartiallyWatched)))); "Removing watched videos, partially watched=" + removePartiallyWatched))));
} }
@ -629,6 +633,43 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
changeThumbnailStreamId(thumbnailStreamId, false); changeThumbnailStreamId(thumbnailStreamId, false);
} }
private void openRemoveDuplicatesDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(this.getActivity());
builder.setTitle(R.string.remove_duplicates_title)
.setMessage(R.string.remove_duplicates_message)
.setPositiveButton(R.string.ok,
(dialog, i) -> removeDuplicatesInPlaylist())
.setNeutralButton(R.string.cancel, null);
builder.create().show();
}
private void removeDuplicatesInPlaylist() {
if (isRewritingPlaylist) {
return;
}
isRewritingPlaylist = true;
showLoading();
final var streamsMaybe = playlistManager
.getDistinctPlaylistStreams(playlistId).firstElement();
disposables.add(streamsMaybe.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(itemsToKeep -> {
itemListAdapter.clearStreamItemList();
itemListAdapter.addItems(itemsToKeep);
setVideoCount(itemListAdapter.getItemsList().size());
saveChanges();
hideLoading();
isRewritingPlaylist = false;
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_BOOKMARK,
"Removing duplicated streams"))));
}
private void deleteItem(final PlaylistStreamEntry item) { private void deleteItem(final PlaylistStreamEntry item) {
if (itemListAdapter == null) { if (itemListAdapter == null) {
return; return;

View file

@ -93,6 +93,11 @@ public class LocalPlaylistManager {
return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io()); return playlistStreamTable.getPlaylistMetadata().subscribeOn(Schedulers.io());
} }
public Flowable<List<PlaylistStreamEntry>> getDistinctPlaylistStreams(final long playlistId) {
return playlistStreamTable
.getStreamsWithoutDuplicates(playlistId).subscribeOn(Schedulers.io());
}
/** /**
* Get playlists with attached information about how many times the provided stream is already * Get playlists with attached information about how many times the provided stream is already
* contained in each playlist. * contained in each playlist.

View file

@ -12,8 +12,14 @@
android:id="@+id/menu_item_rename_playlist" android:id="@+id/menu_item_rename_playlist"
android:title="@string/rename_playlist" android:title="@string/rename_playlist"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/menu_item_remove_watched" android:id="@+id/menu_item_remove_watched"
android:title="@string/remove_watched" android:title="@string/remove_watched"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/menu_item_remove_duplicates"
android:title="@string/remove_duplicates"
app:showAsAction="never" />
</menu> </menu>

View file

@ -632,6 +632,9 @@
<string name="systems_language">System default</string> <string name="systems_language">System default</string>
<string name="remove_watched">Remove watched</string> <string name="remove_watched">Remove watched</string>
<string name="remove_watched_popup_title">Remove watched videos?</string> <string name="remove_watched_popup_title">Remove watched videos?</string>
<string name="remove_duplicates">Remove duplicates</string>
<string name="remove_duplicates_title">Remove duplicates?</string>
<string name="remove_duplicates_message">Do you want to remove all duplicate streams in this playlist?</string>
<string name="remove_watched_popup_warning">Videos that have been watched before and after being added to the playlist will be removed. <string name="remove_watched_popup_warning">Videos that have been watched before and after being added to the playlist will be removed.
\nAre you sure\? This cannot be undone!</string> \nAre you sure\? This cannot be undone!</string>
<string name="remove_watched_popup_yes_and_partially_watched_videos">Yes, and partially watched videos</string> <string name="remove_watched_popup_yes_and_partially_watched_videos">Yes, and partially watched videos</string>